From f3013a4daf5783c9b5145f135cb4a7bf7a258c51 Mon Sep 17 00:00:00 2001 From: andreadelprete <andre.delprete@gmail.com> Date: Tue, 3 Nov 2015 11:59:34 +0100 Subject: [PATCH] Add binary files describing some LP problems in folder test_data. Add test LP solver using binary files. --- .../solver_LP_abstract.hh | 6 +-- include/robust-equilibrium-lib/util.hh | 12 +++-- src/solver_LP_abstract.cpp | 10 ++-- src/static_equilibrium.cpp | 23 ++++++++ src/util.cpp | 12 +++++ test/test_LP_solvers.cpp | 50 +++++++++++++++++- test/test_static_equilibrium.cpp | 6 +-- ...LP_findExtremumOverLine20151103_112611.dat | Bin 0 -> 1248 bytes ...tremumOverLine20151103_112611_solution.dat | Bin 0 -> 64 bytes ...LP_findExtremumOverLine20151103_115627.dat | Bin 0 -> 1248 bytes ...tremumOverLine20151103_115627_solution.dat | Bin 0 -> 64 bytes ...LP_findExtremumOverLine20151103_112610.dat | Bin 0 -> 1336 bytes ...tremumOverLine20151103_112610_solution.dat | Bin 0 -> 152 bytes ...LP_findExtremumOverLine20151103_112611.dat | Bin 0 -> 1336 bytes ...tremumOverLine20151103_112611_solution.dat | Bin 0 -> 152 bytes 15 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 test_data/DLP_findExtremumOverLine20151103_112611.dat create mode 100644 test_data/DLP_findExtremumOverLine20151103_112611_solution.dat create mode 100644 test_data/DLP_findExtremumOverLine20151103_115627.dat create mode 100644 test_data/DLP_findExtremumOverLine20151103_115627_solution.dat create mode 100644 test_data/LP_findExtremumOverLine20151103_112610.dat create mode 100644 test_data/LP_findExtremumOverLine20151103_112610_solution.dat create mode 100644 test_data/LP_findExtremumOverLine20151103_112611.dat create mode 100644 test_data/LP_findExtremumOverLine20151103_112611_solution.dat diff --git a/include/robust-equilibrium-lib/solver_LP_abstract.hh b/include/robust-equilibrium-lib/solver_LP_abstract.hh index 148fdc5..a8555e7 100644 --- a/include/robust-equilibrium-lib/solver_LP_abstract.hh +++ b/include/robust-equilibrium-lib/solver_LP_abstract.hh @@ -54,7 +54,7 @@ public: * @param sol Output solution of the LP. * @return A flag describing the final status of the solver. */ - virtual LP_status solve(const char* filename, Ref_vectorX sol); + virtual LP_status solve(const std::string& filename, Ref_vectorX sol); /** * @brief Write the specified Linear Program to binary file. @@ -70,7 +70,7 @@ public: * @param Aub * @return True if the operation succeeded, false otherwise. */ - virtual bool writeLpToFile(const char* filename, + virtual bool writeLpToFile(const std::string& filename, Cref_vectorX c, Cref_vectorX lb, Cref_vectorX ub, Cref_matrixXX A, Cref_vectorX Alb, Cref_vectorX Aub); @@ -89,7 +89,7 @@ public: * @param Aub * @return True if the operation succeeded, false otherwise. */ - virtual bool readLpFromFile(const char* filename, + virtual bool readLpFromFile(const std::string& filename, VectorX &c, VectorX &lb, VectorX &ub, MatrixXX &A, VectorX &Alb, VectorX &Aub); diff --git a/include/robust-equilibrium-lib/util.hh b/include/robust-equilibrium-lib/util.hh index 5c22c80..759fff9 100644 --- a/include/robust-equilibrium-lib/util.hh +++ b/include/robust-equilibrium-lib/util.hh @@ -66,29 +66,31 @@ namespace robust_equilibrium * Write the specified matrix to a binary file with the specified name. */ template<class Matrix> - void writeMatrixToFile(const char* filename, const Matrix& matrix) + bool writeMatrixToFile(const std::string &filename, const Matrix& matrix) { - std::ofstream out(filename, std::ios::out | std::ios::binary | std::ios::trunc); + std::ofstream out(filename.c_str(), std::ios::out | std::ios::binary | std::ios::trunc); typename Matrix::Index rows=matrix.rows(), cols=matrix.cols(); out.write((char*) (&rows), sizeof(typename Matrix::Index)); out.write((char*) (&cols), sizeof(typename Matrix::Index)); out.write((char*) matrix.data(), rows*cols*sizeof(typename Matrix::Scalar) ); out.close(); + return true; } /** * Read a matrix from the specified input binary file. */ template<class Matrix> - void readMatrixFromFile(const char* filename, Matrix& matrix) + bool readMatrixFromFile(const std::string &filename, Matrix& matrix) { - std::ifstream in(filename, std::ios::in | std::ios::binary); + std::ifstream in(filename.c_str(), std::ios::in | std::ios::binary); typename Matrix::Index rows=0, cols=0; in.read((char*) (&rows),sizeof(typename Matrix::Index)); in.read((char*) (&cols),sizeof(typename Matrix::Index)); matrix.resize(rows, cols); in.read( (char *) matrix.data() , rows*cols*sizeof(typename Matrix::Scalar) ); in.close(); + return true; } /** @@ -116,6 +118,8 @@ namespace robust_equilibrium bool generate_rectangle_contacts(double lx, double ly, Cref_vector3 pos, Cref_vector3 rpy, Ref_matrix43 p, Ref_matrix43 N); + std::string getDateAndTimeAsString(); + } //namespace robust_equilibrium #endif //_ROBUST_EQUILIBRIUM_LIB_UTIL_HH diff --git a/src/solver_LP_abstract.cpp b/src/solver_LP_abstract.cpp index 6bfbadd..2906c9b 100644 --- a/src/solver_LP_abstract.cpp +++ b/src/solver_LP_abstract.cpp @@ -32,7 +32,7 @@ Solver_LP_abstract* Solver_LP_abstract::getNewSolver(SolverLP solverType) return NULL; } -bool Solver_LP_abstract::writeLpToFile(const char* filename, +bool Solver_LP_abstract::writeLpToFile(const std::string& filename, Cref_vectorX c, Cref_vectorX lb, Cref_vectorX ub, Cref_matrixXX A, Cref_vectorX Alb, Cref_vectorX Aub) { @@ -43,7 +43,7 @@ bool Solver_LP_abstract::writeLpToFile(const char* filename, assert(Alb.size()==m); assert(Aub.size()==m); - std::ofstream out(filename, std::ios::out | std::ios::binary | std::ios::trunc); + std::ofstream out(filename.c_str(), std::ios::out | std::ios::binary | std::ios::trunc); out.write((char*) (&n), sizeof(typename MatrixXX::Index)); out.write((char*) (&m), sizeof(typename MatrixXX::Index)); out.write((char*) c.data(), n*sizeof(typename MatrixXX::Scalar) ); @@ -56,11 +56,11 @@ bool Solver_LP_abstract::writeLpToFile(const char* filename, return true; } -bool Solver_LP_abstract::readLpFromFile(const char* filename, +bool Solver_LP_abstract::readLpFromFile(const std::string& filename, VectorX &c, VectorX &lb, VectorX &ub, MatrixXX &A, VectorX &Alb, VectorX &Aub) { - std::ifstream in(filename, std::ios::in | std::ios::binary); + std::ifstream in(filename.c_str(), std::ios::in | std::ios::binary); typename MatrixXX::Index n=0, m=0; in.read((char*) (&n),sizeof(typename MatrixXX::Index)); in.read((char*) (&m),sizeof(typename MatrixXX::Index)); @@ -80,7 +80,7 @@ bool Solver_LP_abstract::readLpFromFile(const char* filename, return true; } -LP_status Solver_LP_abstract::solve(const char* filename, Ref_vectorX sol) +LP_status Solver_LP_abstract::solve(const std::string& filename, Ref_vectorX sol) { VectorX c, lb, ub, Alb, Aub; MatrixXX A; diff --git a/src/static_equilibrium.cpp b/src/static_equilibrium.cpp index 27116eb..e856093 100644 --- a/src/static_equilibrium.cpp +++ b/src/static_equilibrium.cpp @@ -8,6 +8,7 @@ #include <robust-equilibrium-lib/stop-watch.hh> #include <iostream> #include <vector> +#include <ctime> using namespace std; @@ -303,6 +304,18 @@ bool StaticEquilibrium::findExtremumOverLine(Cref_vector2 a, Cref_vector2 a0, do if(lpStatus_primal==LP_STATUS_OPTIMAL) { com = a0 + a*b_p(m); + +#define WRITE_LPS_TO_FILE +#ifdef WRITE_LPS_TO_FILE + string date_time = getDateAndTimeAsString(); + string filename = "LP_findExtremumOverLine"+date_time+".dat"; + if(!m_solver->writeLpToFile(filename.c_str(), c, lb, ub, A, Alb, Aub)) + SEND_ERROR_MSG("Error while writing LP to file "+filename); + filename = "LP_findExtremumOverLine"+date_time+"_solution.dat"; + if(!writeMatrixToFile(filename.c_str(), b_p)) + SEND_ERROR_MSG("Error while writing LP solution to file "+filename); +#endif + return true; } @@ -342,6 +355,16 @@ bool StaticEquilibrium::findExtremumOverLine(Cref_vector2 a, Cref_vector2 a0, do double p = m_solver->getObjectiveValue(); com = a0 + a*p; +#ifdef WRITE_LPS_TO_FILE + string date_time = getDateAndTimeAsString(); + string filename = "DLP_findExtremumOverLine"+date_time+".dat"; + if(!m_solver->writeLpToFile(filename.c_str(), c, lb, ub, A, Alb, Aub)) + SEND_ERROR_MSG("Error while writing LP to file "+filename); + filename = "DLP_findExtremumOverLine"+date_time+"_solution.dat"; + if(!writeMatrixToFile(filename.c_str(), v)) + SEND_ERROR_MSG("Error while writing LP solution to file "+filename); +#endif + // since QP oases cannot detect unboundedness we check here whether the objective value is a large negative value if(m_solver_type==SOLVER_LP_QPOASES && p<-1e7) { diff --git a/src/util.cpp b/src/util.cpp index fa1cede..7ffb2ae 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -7,6 +7,7 @@ #define _ROBUST_EQUILIBRIUM_LIB_CONFIG_HH #include <robust-equilibrium-lib/util.hh> +#include <ctime> namespace robust_equilibrium { @@ -130,6 +131,17 @@ Rotation crossMatrix(Cref_vector3 x) return res; } +std::string getDateAndTimeAsString() +{ + time_t rawtime; + struct tm * timeinfo; + char buffer[80]; + time (&rawtime); + timeinfo = localtime(&rawtime); + strftime(buffer,80,"%Y%m%d_%I%M%S",timeinfo); + return std::string(buffer); +} + } //namespace robust_equilibrium #endif //_ROBUST_EQUILIBRIUM_LIB_CONFIG_HH diff --git a/test/test_LP_solvers.cpp b/test/test_LP_solvers.cpp index eee930d..27faf5d 100644 --- a/test/test_LP_solvers.cpp +++ b/test/test_LP_solvers.cpp @@ -22,6 +22,8 @@ using namespace std; using namespace robust_equilibrium; USING_NAMESPACE_QPOASES +#define EPS 1e-6 + #ifdef CLP_FOUND /** Example addRows.cpp */ void test_addRows() @@ -428,12 +430,58 @@ int main() cout<<"Check constraint matrix A: "<<A.isApprox(A2)<<endl; cout<<"Check constraint lower bound vector Alb: "<<Alb.isApprox(Alb2)<<endl; cout<<"Check constraint upper bound vector Aub: "<<Aub.isApprox(Aub2)<<endl; + } + + { + cout<<"\nTEST QP OASES ON SOME LP PROBLEMS\n"; + string file_path = "../test_data/"; + Solver_LP_abstract *solverOases = Solver_LP_abstract::getNewSolver(SOLVER_LP_QPOASES); + const int PROBLEM_NUMBER = 4; + string problem_filenames[PROBLEM_NUMBER] = {"DLP_findExtremumOverLine20151103_112611", + "DLP_findExtremumOverLine20151103_115627", + "LP_findExtremumOverLine20151103_112610", + "LP_findExtremumOverLine20151103_112611"}; + VectorX c, lb, ub, Alb, Aub, realSol, sol; + MatrixXX A; + for(int i=0; i<PROBLEM_NUMBER; i++) + { + string &problem_filename = problem_filenames[i]; + if(!solverOases->readLpFromFile(file_path+problem_filename+".dat", c, lb, ub, A, Alb, Aub)) + { + SEND_ERROR_MSG("Error while reading LP from file "+problem_filename); + return -1; + } + string solution_filename = problem_filename+"_solution"; + if(!readMatrixFromFile(file_path+solution_filename+".dat", realSol)) + { + SEND_ERROR_MSG("Error while reading LP solution from file "+solution_filename); + return -1; + } + sol.resize(c.size()); + solverOases->solve(c, lb, ub, A, Alb, Aub, sol); + if(sol.isApprox(realSol, EPS)) + { + cout<<"[INFO] Solution of problem "<<problem_filename<<" is equal to the expected value!\n"; + } + else + { + if(fabs(c.dot(sol)-c.dot(realSol))<EPS) + cout<<"[WARNING] Solution of problem "<<problem_filename<<" is different from expected but it has the same cost\n"; + else + { + cout<<"[ERROR] Solution of problem "<<problem_filename<<" is different from the expected value:\n"; + cout<<"\tSolution found "<<sol.transpose()<<endl; + cout<<"\tExpected solution "<<realSol.transpose()<<endl; + cout<<"\tCost found "<<(c.dot(sol))<<endl; + cout<<"\tCost expected "<<(c.dot(realSol))<<endl; + } + } + } return 0; } - #ifdef CLP_FOUND test_addRows(); test_small_LP(); diff --git a/test/test_static_equilibrium.cpp b/test/test_static_equilibrium.cpp index 2834c56..17b9368 100644 --- a/test/test_static_equilibrium.cpp +++ b/test/test_static_equilibrium.cpp @@ -260,8 +260,8 @@ int main() /************************************** USER PARAMETERS *******************************/ double mass = 70.0; double mu = 0.3; // friction coefficient - unsigned int generatorsPerContact = 8; - unsigned int N_CONTACTS = 2; + unsigned int generatorsPerContact = 4; + unsigned int N_CONTACTS = 1; double MIN_FEET_DISTANCE = 0.3; double LX = 0.5*0.2172; // half contact surface size in x direction double LY = 0.5*0.138; // half contact surface size in y direction @@ -272,7 +272,7 @@ int main() RPY_UPPER_BOUNDS << +0*gamma, +0*gamma, +M_PI; double X_MARG = 0.07; double Y_MARG = 0.07; - const int GRID_SIZE = 15; + const int GRID_SIZE = 5; /************************************ END USER PARAMETERS *****************************/ cout<<"Number of contacts: "<<N_CONTACTS<<endl; diff --git a/test_data/DLP_findExtremumOverLine20151103_112611.dat b/test_data/DLP_findExtremumOverLine20151103_112611.dat new file mode 100644 index 0000000000000000000000000000000000000000..b22aa57c927127ef59bb14e8f00c5d1fb545e64d GIT binary patch literal 1248 zcmZQ$fB-=#y-_!1)4F8lz3JWRaeLL6Z4(OE9XO@J4^*h!c^CQV%l;M1r|)0BTGyec zplj~SnOAIp+7#j!FdZV1c7m!Wh6cOriqp}g7s;3Gw;%EFZ(jCp|MkD26C4*U+V67Z zV_T`lYWqV^W4Bj&Pq2rn>zDozE$>-k15@9(?E#Ny##(zVB`(g*XAaqS<|^dfTr$xf zqOQdTs&79`{oY%xuV%#@ws(V?e-L8+ewey2%@>^ObNlwd)K7$(uk^r0?BT;D`@10K z?*}^`NWk0$Q$OqQ#QQh<j_$v>A!UQb%jNdRbA_zRH}>FkKcj9(L5IZV{eCyoE<gTr z&^~?6wP4#<)Ar+X|A{R=72TUA+h2y7e-dIoPWLlF&7aDA>5Z)7iv4L2^X+lD-`#Bb z**8~@?PuAzXwd=B3-;MpV!mze-i*`z(L$4b)-Bt*e`3358MpT}`^%5S`ptjO#OeM6 zc1{8>J~!E;nUB-`QBd>WZTUX;^@Jn#7a->2bbrr@Y>(c3%l8+Bme{V?c)?zY;ZsdA z#}=IKzxn!9$Vu-N_W!k|1wU`PW^dznAa$R5FV6HMkkRm`UG$JWn)x{0e+O#*slT%> z?@Bmo?+7s;o_=8Q0Hckf&$Xp_B^~&-c8_f0)rtd9@dlXuz|tQ;?go_$#N=;UsRKIU G!+roTDcv#v literal 0 HcmV?d00001 diff --git a/test_data/DLP_findExtremumOverLine20151103_112611_solution.dat b/test_data/DLP_findExtremumOverLine20151103_112611_solution.dat new file mode 100644 index 0000000000000000000000000000000000000000..de3853d919e0105c791f2fe9e829c8b57c4cef4a GIT binary patch literal 64 zcmZQ$fB;4)&9KGc)b~v9{R|C@rYsa>-Ounro6TmSqCG={cvFd@!hVKRP0GT5Q|uwi HKiL2P%VrRl literal 0 HcmV?d00001 diff --git a/test_data/DLP_findExtremumOverLine20151103_115627.dat b/test_data/DLP_findExtremumOverLine20151103_115627.dat new file mode 100644 index 0000000000000000000000000000000000000000..15d24c756d1aad60ffaadcf9f299578f5be0f390 GIT binary patch literal 1248 zcmZQ$fB-=#eOotW)4F8ly%W0C<MygC+bZO<J8(*cA9zwxqNkjoaNtJgR@-<zDF=wU z%Qir53h@h=4v|PZLDdsOgI#vT>1fi6<V*J3k9hbuFMGE?BWzk*zW=HH*C!<Gx)*cA zp7CMvn!@}Q_Aqt*(jTJbJxgq0>L-@`v0GApbpN`dUyQ3vPTE(gCP^GUv~oX0U5gD= z-+q|-U6Yc&-<W>@Zax#l{QWR>VVW;E*XQ=_fvJa?&k)!9RE=xDeI>+vd$8ky1k7D9 z_2yU9ZhUszyuY1AhV_8mar=DMj+|#tR^xQPpyIQMyYv?Ck9)S&r(5id{rxESNB3FU zak`&DZL@=$rG-73`8eG#05yMuQq$fqX@~4@L(IqNes|_mTooBh_KSXea$4u>^!?9v z2}PgZUjsAW9zFb*?o&UuZc4&_FY7B0qbBv*AAXY5KJTeBPWKmcvl=}LU1TqUYCbG{ z_M?aY1gQBh{ztYPj$FV0G{k(I?w8Lsohy9o=>7or2NR^5mhabne{=0F(;l4e_voG- z>hbQt{xkm{o!r^ous`h9+nLINOK`gXQ4q7tFP$Cx{h{V-LCnYLewg`T&liU;DcA}# zAMSowI)c$g(dXLIypj%lTf0X#@oL2ZsCWZReqiYj`+@d@$^~NbH?7nGo$z5l0EmU! AhyVZp literal 0 HcmV?d00001 diff --git a/test_data/DLP_findExtremumOverLine20151103_115627_solution.dat b/test_data/DLP_findExtremumOverLine20151103_115627_solution.dat new file mode 100644 index 0000000000000000000000000000000000000000..d1684bd0081edc83e4e41ea4ea7caf55e05c95b0 GIT binary patch literal 64 zcmZQ$fB;4)&Cqap?J-01{R}UX$~?IQ_A}HhRM+|fqzf`{mzCJ<XV9rx^M)nO9<1CV GcrO6oa}m@4 literal 0 HcmV?d00001 diff --git a/test_data/LP_findExtremumOverLine20151103_112610.dat b/test_data/LP_findExtremumOverLine20151103_112610.dat new file mode 100644 index 0000000000000000000000000000000000000000..0071f421913be82d09807d6360b1671f39966b2b GIT binary patch literal 1336 zcmWe+fB-fqJvgWj`=Rb=7*IOv#{r=I3h@h=oCX064vH&IN0VM8U$XC){tzwi*<u6b z!@`S!zVan-`@=L}aIVko+k<L9_2t2V03@~_@$he6_HO?`(J*&h{~J2NanYjvecK-J zh-R#{-+Qa|)vTDq_7kD}S%)Xyzu9+mKcj9(L5IZV{U^5gRCI5eY|jAYyPHiv`{wGg z{n0{`ebz18y8nQklfaA5P4-bxe$R<)kKTRD_uqVdD&(a13VVT!hCl71hwSe_`KL|a zrb-009&owxv8_~NwY`=S7w6_PhwR;;e5D62Vh<lK*?)0E$_9&<%kBMcrd@vg=b-&% zD1R#Rr8lyQEB3Q&T(szb=LP$T?Ve@a-q-A5{C8Wv&wV}Nh<#CLiS3Gw7wrFQOACJ9 zbPdiw^>^0gT?t3+xm&Z2%h!ZC9C{kNz0!MveP^yh-pwTw?GHlu`@10g{l{~Ktjag` z*r(6A7Hs=!+WwOe{{A!w-#+_F%(u<mo9!<@66-hrJ##;lzyAV+Z?DAgsV13Yi@lBC zfz*BKy)gcMM+hID4?e89xM$_dcYERwIEOWKyt7HyxT9((TXSFr|J<*)Y9$Y3G+cdg Tmp91aL9)M&QqBPzQq=<hm$U4h literal 0 HcmV?d00001 diff --git a/test_data/LP_findExtremumOverLine20151103_112610_solution.dat b/test_data/LP_findExtremumOverLine20151103_112610_solution.dat new file mode 100644 index 0000000000000000000000000000000000000000..6a642a25eeddb2063090419fd83f76057f4558be GIT binary patch literal 152 zcmWe+fB;4)-DbsbUNttr;bhI6b09i1R2R&D(&&B(#D^$L*bP;VgDSAbE|IXi#rwPT i-=Cfickgq_f#|)WbHV(jSGS0P_zz8PeK=CS+#UdzrzSxF literal 0 HcmV?d00001 diff --git a/test_data/LP_findExtremumOverLine20151103_112611.dat b/test_data/LP_findExtremumOverLine20151103_112611.dat new file mode 100644 index 0000000000000000000000000000000000000000..f2d649d86d6ad499229c13b0070b61069c28a23b GIT binary patch literal 1336 zcmWe+fB-fqJvgWj`=Rb=7*IOv#{r=I3h@h=oCX064vH&IN0VM8U$XC){tzwi*<u6b z!@`S!zVan-`@=L}aIVko+k<L9_2t2V03@~_@$he6_HO?`(J*&h{~J2NanYjvecK-J zh-R#{-+Qa|)vTDq_7kD}S%)Xyzu9+mKcj9(L5IZV{U^5gRCI5eY|jAYyPHiv`{wGg z{n0{`ebz18y8nQklfaA5P4-bxe$R<)kKTRD_uqVdD&(a13VVT!hCl71hwSe_`I<_B zT~+T354c?U*jB2s+FnbEi*xgtL-uY^zS091v4;<r?7z4nWrM}b<@SCz(=I>$bI|@W zls}dE(i>UD75iB>E?RWJ^Md`vcF!_y?`!rj{<|&T=f0kB#J(uB#CFBT3-<rDr3F84 zx(4T;`aA3Lu7soZ{AW6?Y$U}T4n2+CUg<r-zB5-L@8*(;_6H&S{aq0L{^PkqR^=Oe z?9=C53$}eVZU0FKe}5W;Z=Zc7=G*4(&GwfciS?WRp1B{&-+uwZw^w5LRFllH#oos6 zK<YmAUKoGBBZLpn2f;qRugtU`>`9lcdVli6eVYY+mXZb28V@i9vR2%EB7fkE>CUdT S)507i6hB40(%oo7s(JvC4d_t- literal 0 HcmV?d00001 diff --git a/test_data/LP_findExtremumOverLine20151103_112611_solution.dat b/test_data/LP_findExtremumOverLine20151103_112611_solution.dat new file mode 100644 index 0000000000000000000000000000000000000000..1672cfe8e4f4ad7b5a9c70c02fccd6c2d3b073b5 GIT binary patch literal 152 zcmWe+fB;4){YG*1vdgN$4xU#$mx5@OLw(CZbn>b*+d(u`8QD}(-Zi<d$$<_(PJ}6e ZXp3{7WI^=3PnY>Y^poZ_*3Hoe?E%l0BMJZj literal 0 HcmV?d00001 -- GitLab