/[PAMELA software]/yoda/event/PamelaRun.cpp
ViewVC logotype

Contents of /yoda/event/PamelaRun.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 6.10 - (show annotations) (download)
Mon Oct 16 16:02:20 2006 UTC (18 years, 1 month ago) by mocchiut
Branch: MAIN
CVS Tags: YODA6_3/19, YODA6_3/18, YODA6_3/13, YODA6_3/17, YODA6_3/16, YODA6_3/15, YODA6_3/14, YODA6_3/20, HEAD
Changes since 6.9: +4 -2 lines
Error occurred while calculating annotation data.
libyoda.so linking bug fixed, do not print on screen tracker errors

1 /** @file
2 * $Author: pam-rm2 $
3 * $Date: 2006/08/23 14:39:44 $
4 * $Revision: 6.8 $
5 *
6 * Implementation of the PamelaRun class.
7 */
8 #include <iostream>
9 #include <iomanip>
10 #include <list>
11 #include <exception>
12 #include <log4cxx/logger.h>
13
14 #include <TFile.h> //Substituted by Maurizio 05 Feb 2004
15 #include <TTree.h> //Substituted by Maurizio 05 Feb 2004
16 #include <TChain.h> //Substituted by Maurizio 05 Feb 2004
17 #include <TKey.h> //Substituted by Maurizio 05 Feb 2004
18 #include <TList.h> //Substituted by Maurizio 05 Feb 2004
19
20
21 #include "PamelaRun.h"
22 #include "EventHeader.h"
23 #include "Algorithm.h"
24 #include "AlgorithmInfo.h"
25 #include "Exception.h"
26 #include <sys/stat.h>
27
28 extern "C" {
29 #include <dirent.h>
30 #include "DirectoryStructure.h"
31 }
32
33 #include "yodaversion.h"
34 //#define version 60313
35 //extern int GetYODAver();
36
37 using namespace pamela;
38
39 static log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger(_T("pamela.PamelaRun"));
40
41 /**
42 * Get the run name according to a certain run number.
43 * @param run Run number.
44 * @return a string with the run name.
45 */
46 std::string PamelaRun::GetRunName(int run) {
47 std::stringstream temp;
48 temp.str("");
49 temp << std::setw( 4 ) << std::setfill( '0' ) << run;
50 return "Run" + temp.str();
51 }
52
53 /**
54 * Check if the run number is already assigned
55 * @param int run - Number of the run to check
56 * @return int
57 */
58 void PamelaRun::RunExists(std::string input) throw (std::exception){
59 int res;
60 DIR *dirp;
61 std::string fileName;
62 //std::string pathDir((char*)getenv("YODA_DATA"));
63 std::string pathDir(GetPath());
64 std::string tempName;
65 std::string::size_type pos;
66
67 pos = input.find_last_of("/");
68 fileName = input.substr(pos+1);
69 pos = fileName.find_last_of(".");
70 fileName = fileName.substr(0,pos);
71 pathDir = pathDir + "/";
72 tempName = pathDir + fileName;
73 oss.str("");
74 oss << Path << "/" << fileName;
75 UnpackPath = oss.str();
76 oss.str("");
77 oss << fileName;
78 Run = oss.str();
79 }
80
81 /**
82 * Create a new Pamela run.
83 * @param run Run number
84 * @param path Base path name to the data
85 */
86 PamelaRun::PamelaRun(std::string fileName, std::string path, bool multiple, short compr):
87 Path(path),
88 Run(fileName),
89 RunNumber(1),//veirificare se รจ possibilie eliminare questa variabile
90 Multiple(multiple),
91 SingleFile(0),
92 compression(compr){
93 logger->debug(_T("Constructor"));
94 //Run = RunExists(fileName);
95 RunExists(fileName);
96 info = RunInfo(this);
97 }
98
99 /**
100 * Get the directory name that contains the files of a certain event
101 * type for this run.
102 * @param type the packet type.
103 * @return the complete path for this event type.
104 */
105 std::string PamelaRun::GetDirName(PacketType const * type) const {
106 //std::stringstream oss;
107 //std::string name = type->GetName();
108 //oss.str("");
109 //oss << Path << "/" << Run << "/" << "pippo";
110 //return oss.str();
111 //return "pippo";
112 //std::string EventType = type->GetName();
113 return Path + "/" + Run + "/" + type->GetName();
114 }
115
116 /**
117 * Get the file name for a certain event type.
118 * @param type subpacket type.
119 * @param name subpacket name.
120 * @return the complete path and file name.
121 */
122 std::string PamelaRun::GetFileName(const SubPacket* type, std::string name) {
123 if (type->IsDetectorSpecific()) {
124 return UnpackPath + "/" + type->GetPacketType()->GetName() + "/"
125 + Run + "." + type->GetPacketType()->GetName() + "."
126 + type->GetSubDetector()->GetName() + "."
127 + name + ".root";
128 } else {
129 return UnpackPath + "/" + type->GetPacketType()->GetName() + "/"
130 + Run + "." + type->GetPacketType()->GetName() + "."
131 + name + ".root";
132 /*
133 return Path + "/" + type->GetPacketType()->GetName() + "/"
134 + Run + "." + type->GetPacketType()->GetName() + "."
135 + name + ".root"; */
136 }
137 }
138
139 /**
140 * Get the file name for a certain event type.
141 * @param type subpacket type.
142 * @return the complete path and file name.
143 */
144 std::string PamelaRun::GetFileName(const SubPacket* type) {
145 //return GetFileName(type, type->GetSubPacketName());
146 return GetFileName(type, "pippo");
147 }
148
149 /**
150 * Get the branch name that corresponds to a certain
151 * subpacket. Usually, this is the name of the subpacket.
152 * @param type subpacket type.
153 * @return the corresponding branch name.
154 */
155 static std::string GetDefaultBranchName(const SubPacket* type) {
156 return type->GetSubPacketName();
157 }
158
159 /**
160 * Get the tree name for a certain subpacket. This is the name of the
161 * subdetector for detector specific subpackets, and the packet name
162 * ("Physics" etc.) for common packets.
163 * @param type the packet type.
164 * @return the corresponding tree name.
165 */
166 static std::string GetTreeName(const SubPacket* type) {
167 if (type->IsDetectorSpecific()) {
168 return type->GetSubDetector()->GetName();
169 } else {
170 return type->GetPacketType()->GetName();
171 }
172 }
173
174 /**
175 * Get a list of all root files from a certain path.
176 * @param path Path name to start the search.
177 * @return A list of all file names.
178 */
179 static std::list<std::string> GetRootFiles(std::string path) throw (std::exception) {
180 std::list<std::string> files;
181 DIR *dir = opendir(path.c_str());
182 if (dir == 0) {
183 logger->debug("Could not open " + path);
184 //throw Exception("Could not open " + path);
185 }
186
187 for (struct dirent *d = readdir(dir); d != NULL; d = readdir(dir)) {
188 if ((strcmp(".",d->d_name) == 0) || (strcmp("..",d->d_name) == 0))
189 continue;
190 std::string filename = path + "/" + d->d_name;
191 struct stat buf;
192 stat(filename.c_str(), &buf);
193 if S_ISDIR(buf.st_mode) {
194 std::list<std::string>toAdd = GetRootFiles(filename);
195 files.insert(files.end(), toAdd.begin(), toAdd.end());
196 } else if ((filename.substr(filename.size()-5, filename.size())
197 == ".root") // correct suffix
198 && (!isdigit(filename[filename.size()-6]))) { // base file
199 std::string nextfilename = filename;
200 nextfilename.insert(filename.size()-5, "_1");
201 if (stat(nextfilename.c_str(), &buf) == 0) {
202 filename.insert(filename.size()-5, "*");
203 }
204 logger->debug("Using " + filename);
205 files.push_back(filename);
206 }
207 }
208 return files;
209 }
210
211 /**
212 * Helper function to open the ROOT TTree of the header within the run
213 * framework.
214 * @param type The packet type.
215 * @return the ROOT TTree.
216 */
217 TChain* PamelaRun::ReadHeaderTree(const PacketType *type)
218 throw (std::exception) {
219 EventHeader header(type);
220 std::string FileName = GetFileName(&header);
221 std::string TreeName = GetTreeName(&header);
222 TChain *tree = new TChain(TreeName.c_str());
223 tree->Add(FileName.c_str());
224 return tree;
225 }
226
227 /**
228 * All add trees of a file as friend to a given tree. The tree name of
229 * each tree found in the file is used as the name of the friend alias.
230 * @param tree The base tree to add all other as friends.
231 * @param FileName The name of the file with other trees.
232 */
233 static void AddAllAsFriend(TChain* tree, std::string FileName) {
234 std::string BaseFileName = FileName;
235 bool HaveChain = false;
236 if (BaseFileName[BaseFileName.size()-6] == '*') {
237 BaseFileName.erase(BaseFileName.size()-6, 1);
238 HaveChain = true;
239 }
240 TFile* File = new TFile(BaseFileName.c_str(), "read");
241 if (!File->IsOpen()) {
242 logger->error("Could not open file " + BaseFileName + " for reading.");
243 return;
244 }
245 TList* list = File->GetListOfKeys();
246 if (HaveChain) {
247 for (TIter i(list); TKey* k = (TKey*)i();) {
248 if (std::string(TTree::Class()->GetName()) == k->GetClassName()) {
249 std::string TreeName = k->GetName();
250 TChain* FriendChain = new TChain(TreeName.c_str());
251 FriendChain->Add(FileName.c_str());
252 tree->AddFriend(FriendChain, TreeName.c_str());
253 //logger->warn("Adding chain " + TreeName + " with " + FriendChain->GetEntries() + " entries as Friend");
254 }
255 }
256 File->Close();
257 } else {
258 for (TIter i(list); TKey* k = (TKey*)i();) {
259 if (std::string(TTree::Class()->GetName()) == k->GetClassName()) {
260 std::string TreeName = k->GetName();
261 TTree* FriendTree = (TTree *)File->Get(TreeName.c_str());
262 tree->AddFriend(FriendTree, TreeName.c_str());
263 //logger->debug("Adding tree " + TreeName + " with " + FriendTree->GetEntries() + " entries as Friend");
264 }
265 }
266 }
267 }
268
269 /**
270 * Read all Root TTrees which belong to a certain event type and mount them
271 * together as "friends".
272 * @param type The packet type.
273 * @return The root trees with the friends.
274 */
275 TTree* PamelaRun::ReadTTree(const PacketType* type) {
276 oss.str("");
277 oss << "Getting root files of " << type->GetName();
278 logger->debug(oss.str().c_str());
279 RootTreeMap::iterator t = TTreeMap.find(type);
280 if (t != TTreeMap.end()) {
281 return t->second;
282 } else {
283 oss.str("");
284 oss << "Reading root files of " << type->GetName();
285 logger->debug(oss.str().c_str());
286 EventHeader header(type);
287 std::string HeaderFileName = GetFileName(&header);
288 TChain* HeaderTree = ReadHeaderTree(type);
289 std::list<std::string> rootfiles = GetRootFiles(GetDirName(type));
290 for (std::list<std::string>::iterator i = rootfiles.begin();
291 i != rootfiles.end(); i++){
292 if (*i == HeaderFileName)
293 continue; // dont add the header tree itself.
294 AddAllAsFriend(HeaderTree, *i);
295 }
296 TTreeMap.insert(RootTreeMap::value_type(type, HeaderTree));
297 return HeaderTree;
298 }
299 }
300
301 /**
302 * Register a certain SubPacket, identified by its name, to be read
303 * from the repository. This function is made for interactive work.
304 * @param subpacket A pointer to the pointer of the packet.
305 * @param name The name of the subpacket
306 */
307 void PamelaRun::ReadSubPacket(void* subpacket, std::string name) {
308 SubPacket *packet = *(SubPacket**)subpacket;
309
310 // look into the map of subpackets if we already read it.
311 std::string FullName = packet->GetPacketType()->GetName() + "." + name;
312 SubPacketMap::iterator i = SubPacketAddresses.find(FullName);
313 if (i != SubPacketAddresses.end()) { // it is in the map
314 *(SubPacket**)subpacket = i->second;
315 return;
316 } else { // not found in the map of used subpackets
317 TTree* tree = ReadTTree(packet->GetPacketType());
318 TBranch* branch = tree->GetBranch(name.c_str());
319 if (branch != 0) {
320 branch->SetAddress(subpacket);
321 SubPacketAddresses.insert(SubPacketMap::value_type(FullName, packet));
322 } else {
323 oss.str("");
324 oss << "Could not find data for " << packet->GetPacketType()->GetName() << "/" << name ;
325 logger->error(oss.str().c_str());
326 }
327 }
328 }
329
330 /**
331 * Register a certain SubPacket, identified by its default name, to be
332 * read from the repository. This function is made for
333 * interactive work.
334 * @param subpacket A pointer to the pointer of the packet.
335 */
336 void PamelaRun::ReadSubPacket(void* subpacket) {
337 SubPacket *packet = *(SubPacket**)subpacket;
338 ReadSubPacket(subpacket, GetDefaultBranchName(packet));
339 }
340
341 /**
342 * Register a certain SubPacket with its default name, to be read from
343 * the repository. This functions is for use from the algorithm.
344 * @param algo Algorithm that needs this SubPacket.
345 * @param subpacket A pointer to the pointer of the packet.
346 */
347 void PamelaRun::ReadSubPacket(const Algorithm* algo, void* subpacket) {
348 //:TODO: store the request of the algorithm here.
349 ReadSubPacket(subpacket);
350 }
351
352 /**
353 * Register a certain SubPacket, with a custom name, to
354 * be read from the repository. This functions is for use from
355 * the algorithm.
356 * @param algo Algorithm that needs this SubPacket.
357 * @param subpacket A pointer to the pointer of the packet.
358 * @param name The name of the subpacket
359 */
360 void PamelaRun::ReadSubPacket(const Algorithm* algo, void* subpacket,
361 std::string name) {
362 //:TODO: store the request of the algorithm here.
363 ReadSubPacket(subpacket, name);
364 }
365
366 /**
367 * Helper function to create a ROOT TTree within the run framework.
368 * @param algo Algorithm that creates this SubPacket.
369 * @param packet subpacket type
370 * @param name the name of the subpacket
371 * @return the ROOT TTree.
372 */
373 TTree* PamelaRun::CreateTTree(Algorithm* algo, const SubPacket* packet,
374 std::string name)
375 throw (std::exception) {
376 std::string FileName = "";
377 std::string EventType = packet->GetPacketType()->GetName();
378 TFile* File = 0;
379 std::string TreeName = GetTreeName(packet);
380 TTree *tree = 0;
381 FileName = GetFileName(packet, name);
382 CreateDirectoryStructure(FileName.c_str());
383 File = new TFile(FileName.c_str(), "create");
384 tree = new TTree(TreeName.c_str(), algo->GetAlgorithmName().c_str());
385 WritingRootTrees[packet->GetPacketType()].push_back(tree);
386
387 File->SetCompressionLevel(compression);
388 if (!File->IsOpen()) {
389 logger->error("Could not open file " + FileName);
390 //throw Exception("Could not open file " + FileName);
391 }
392 logger->debug("Creating file " + FileName + " with Tree " + TreeName);
393 return tree;
394 }
395
396
397 /**
398 * Register a certain SubPacket to be written to the repository. A
399 * usual call sequence for this function ist
400 * MyEvent *event = new MyEvent();
401 * run->WriteSubPacket(this, &event, event->Class()
402 * @param algo Algorithm that produces this SubPacket.
403 * @param subpacket A pointer to the pointer of the subpacket.
404 * @param c The class the subpacket belongs to.
405 * @param name The name of the packet.
406 */
407 void PamelaRun::WriteSubPacket(Algorithm *algo, void* subpacket,
408 const TClass *c, std::string name) {
409 SubPacket *packet = *(SubPacket **)subpacket;
410 oss.str("");
411 oss << "Register: " << name << " for " << algo->GetAlgorithmName() << " (writing)";
412 logger->debug(oss.str().c_str());
413 TTree* HeaderTree = ReadTTree(packet->GetPacketType());
414 std::string FullName = packet->GetPacketType()->GetName() + "." + name;
415 if (Multiple) {
416 TTree* tree = CreateTTree(algo, packet, name);
417 oss.str("");
418 oss << "Branch: " << name << " Class: " << c->GetName();
419 logger->debug(oss.str().c_str());
420 tree->Branch(name.c_str(), c->GetName(), subpacket);
421 HeaderTree->AddFriend(tree, tree->GetName());
422 SubPacketAddresses.insert(SubPacketMap::value_type(FullName, packet));
423 } else {
424 std::string nameBranch(name);
425 HeaderTree->Branch(nameBranch.c_str(), c->GetName(), subpacket);
426 SubPacketAddresses.insert(SubPacketMap::value_type(FullName, packet));
427 }
428 }
429
430 /**
431 * Register a certain SubPacket with its default name to be written to
432 * the repository. A usual call sequence for this function ist
433 * MyEvent *event = new MyEvent();
434 * run->WriteSubPacket(this, &event, event->Class()
435 * @param algo Algorithm that produces this SubPacket.
436 * @param subpacket A pointer to the pointer of the subpacket.
437 * @param c The class the subpacket belongs to.
438 */
439 void PamelaRun::WriteSubPacket(Algorithm *algo, void* subpacket,
440 const TClass *c) {
441 SubPacket *packet = *(SubPacket **)subpacket;
442 WriteSubPacket(algo, subpacket, c, GetDefaultBranchName(packet));
443 }
444
445 /**
446 * Write the header packet to all ROOT files in the tree. Intended to
447 * be used for raw data readers that create the initial event structure.
448 * @param algo Algorithm that produces this SubPacket.
449 * @param subpacket A pointer to the pointer of the header packet.
450 */
451 void PamelaRun::WriteHeaders(Algorithm* algo, EventHeader** subpacket) {
452 WriteHeader(algo, subpacket, PacketType::PhysEndRun);
453 WriteHeader(algo, subpacket, PacketType::CalibCalPulse1);
454 WriteHeader(algo, subpacket, PacketType::CalibCalPulse2);
455 WriteHeader(algo, subpacket, PacketType::Physics);
456 WriteHeader(algo, subpacket, PacketType::CalibTrkBoth);
457 WriteHeader(algo, subpacket, PacketType::CalibTrk1);
458 WriteHeader(algo, subpacket, PacketType::CalibTrk2);
459 WriteHeader(algo, subpacket, PacketType::CalibTof);
460 WriteHeader(algo, subpacket, PacketType::CalibS4);
461 WriteHeader(algo, subpacket, PacketType::CalibCalPed);
462 WriteHeader(algo, subpacket, PacketType::Calib1_Ac1);
463 WriteHeader(algo, subpacket, PacketType::Calib1_Ac2);
464 WriteHeader(algo, subpacket, PacketType::Calib2_Ac1);
465 WriteHeader(algo, subpacket, PacketType::Calib2_Ac2);
466 WriteHeader(algo, subpacket, PacketType::RunHeader);
467 WriteHeader(algo, subpacket, PacketType::RunTrailer);
468 WriteHeader(algo, subpacket, PacketType::CalibHeader);
469 WriteHeader(algo, subpacket, PacketType::CalibTrailer);
470 WriteHeader(algo, subpacket, PacketType::InitHeader);
471 WriteHeader(algo, subpacket, PacketType::InitTrailer);
472 WriteHeader(algo, subpacket, PacketType::EventTrk);
473 WriteHeader(algo, subpacket, PacketType::Log);
474 WriteHeader(algo, subpacket, PacketType::VarDump);
475 WriteHeader(algo, subpacket, PacketType::ArrDump);
476 WriteHeader(algo, subpacket, PacketType::TabDump);
477 WriteHeader(algo, subpacket, PacketType::Tmtc);
478 WriteHeader(algo, subpacket, PacketType::Mcmd);
479 WriteHeader(algo, subpacket, PacketType::ForcedFECmd);
480 WriteHeader(algo, subpacket, PacketType::Ac1Init);
481 WriteHeader(algo, subpacket, PacketType::CalInit);
482 WriteHeader(algo, subpacket, PacketType::TrkInit);
483 WriteHeader(algo, subpacket, PacketType::TofInit);
484 WriteHeader(algo, subpacket, PacketType::TrgInit);
485 WriteHeader(algo, subpacket, PacketType::NdInit);
486 WriteHeader(algo, subpacket, PacketType::S4Init);
487 WriteHeader(algo, subpacket, PacketType::Ac2Init);
488 WriteHeader(algo, subpacket, PacketType::CalAlarm);
489 WriteHeader(algo, subpacket, PacketType::Ac1Alarm);
490 WriteHeader(algo, subpacket, PacketType::TrkAlarm);
491 WriteHeader(algo, subpacket, PacketType::TrgAlarm);
492 WriteHeader(algo, subpacket, PacketType::TofAlarm);
493 WriteHeader(algo, subpacket, PacketType::S4Alarm);
494 WriteHeader(algo, subpacket, PacketType::Ac2Alarm);
495 WriteHeader(algo, subpacket, PacketType::TsbT);
496 WriteHeader(algo, subpacket, PacketType::TsbB);
497 }
498
499 /**
500 * Write the ROOT files to disk.
501 */
502 void PamelaRun::WriteFiles(void) {
503 // Workaround: unlink all friend trees first top avoid to store
504 // the links in the header tree file.
505 for (RootTreeMap::iterator i = TTreeMap.begin(); i != TTreeMap.end(); i++) {
506 if (i->second->GetListOfFriends() != 0) {
507 i->second->GetListOfFriends()->Delete();
508 }
509 }
510
511 TTree *softinfo = 0;
512 Int_t version=GetYODAver();
513 //Int_t version= 60312;
514 //printf("version = %f \n",version);
515 softinfo = new TTree("SoftInfo","YODA software info");
516 softinfo->Branch("SoftInfo",&version,"version/I");
517 softinfo->Fill();
518
519 for (TTreeListMap::iterator i = WritingRootTrees.begin();
520 i != WritingRootTrees.end(); i++) {
521 for (TTreeList::iterator j = i->second.begin();
522 j != i->second.end(); j++) {
523 (*j)->GetCurrentFile()->Write(0,TObject::kOverwrite);
524 }
525 }
526
527 softinfo->Delete();
528 }
529
530 /**
531 * Fill all ROOT trees of a certain type that were opened for writing.
532 * @param type the package type of the trees to fill.
533 */
534 void PamelaRun::FillTrees(const PacketType* type) {
535 try{
536 for (TTreeList::iterator j = WritingRootTrees[type].begin();
537 j != WritingRootTrees[type].end(); j++) {
538 (*j)->Fill();
539 }
540 } catch(Exception exc){
541 logger->fatal("ORRORE!!!");
542 }
543 }
544
545
546 /**
547 * Write the header packet of a specified packet type. This is mainly used
548 * for the raw reader to create the base for the event structure.
549 * @param algo Algorithm that produces this SubPacket.
550 * @param subpacket A pointer to the pointer of the packet.
551 * @param type The packet type.
552 */
553 void PamelaRun::WriteHeader(Algorithm* algo, EventHeader** subpacket,
554 const PacketType* type) {
555 EventHeader header(type);
556 std::string EventType = (&header)->GetPacketType()->GetName();
557
558 std::string FileName = "";
559 TFile* File = 0;
560 Long64_t maxsize = 5000000000LL;
561 TTree::SetMaxTreeSize(maxsize);
562 TTree *tree = 0;
563 if ( Multiple ){
564 FileName = GetFileName(&header, GetDefaultBranchName(&header));
565 CreateDirectoryStructure(FileName.c_str());
566 File = new TFile(FileName.c_str(), "create");
567 File->SetCompressionLevel(compression);
568 tree = new TTree("Pscu", algo->GetAlgorithmName().c_str());
569 WritingRootTrees[(&header)->GetPacketType()].push_back(tree);
570 tree->GetCurrentFile()->cd();
571 tree->Branch(GetDefaultBranchName(&header).c_str(),
572 (*subpacket)->Class()->GetName(), subpacket);
573 TTreeMap.insert(RootTreeMap::value_type(type, tree));
574 } else {
575 if (!Multiple && (SingleFile == NULL)){
576 //std::string pathDir((char*)getenv("YODA_DATA"));
577 SingleFile = new TFile((GetPath() + "/" + Run + ".root").c_str(), "update");
578 SingleFile->SetCompressionLevel(compression);
579 }
580 tree = new TTree(EventType.c_str(), algo->GetAlgorithmName().c_str());
581 WritingRootTrees[type].push_back(tree);
582 tree->GetCurrentFile()->cd();
583 tree->Branch(GetDefaultBranchName(&header).c_str(),
584 (*subpacket)->Class()->GetName(), subpacket);
585 TTreeMap.insert(RootTreeMap::value_type(type, tree));
586 }
587 }

  ViewVC Help
Powered by ViewVC 1.1.23