diff --git a/.gitignore b/.gitignore index c944978..46a34d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,22 @@ /stubwarnings.txt /gdx.vcxproj.user +/out/ /build/ /x64/ -/cmake-build-debug-coverage/ -/cmake-build-release-coverage/ -/cmake-build-release/ -/cmake-build-debug/ +/gdx/x64/ +/cmake-build-*/ .idea/ .vs/ +.vscode/ /slowdown.txt /class_benchmarks.txt /gdxbench_collected.txt -/.vscode/ /gdxtest.xcodeproj/project.xcworkspace/xcuserdata/ /gdxtest.xcodeproj/xcuserdata/ /zlib/ /src/gclgms.h /src/gdxcc.c /src/gdxcc.h +/src/__pycache__ /venv/ /docs/html/ -/gdx/x64/ -/src/__pycache__ \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2f03b7c..49549bc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,6 +5,7 @@ stages: - build - analyze - test + # - test-gdxtools - leak-check - perf-check - docs @@ -100,10 +101,12 @@ build-leg: - cmake --build . 2>&1 | tee build_log.txt - python3 ciscripts/report_for_log.py gcc build_log.txt warnings.xml - mv libgdx-static.a libgdx-linux.a + - mkdir -p ./build + - mv {gdxdump,gdxdiff,gdxmerge} ./build needs: [fetch-ci-scripts,apigenerator] artifacts: name: gdx-leg - paths: [gdxtest,gdxwraptest,libgdxcclib64.so,libgdx-linux.a] + paths: [gdxtest,gdxwraptest,libgdxcclib64.so,libgdx-linux.a,build] expire_in: 2 hours reports: junit: warnings.xml @@ -138,10 +141,12 @@ build-deg: - cmake --build . 2>&1 | tee build_log.txt - python3 ciscripts/report_for_log.py clang build_log.txt warnings.xml - mv libgdx-static.a libgdx-macos.a + - mkdir -p ./build + - mv {gdxdump,gdxdiff,gdxmerge} ./build needs: [fetch-ci-scripts,apigenerator] artifacts: name: gdx-deg - paths: [gdxtest,gdxwraptest,libgdxcclib64.dylib,libgdx-macos.a] + paths: [gdxtest,gdxwraptest,libgdxcclib64.dylib,libgdx-macos.a,build] expire_in: 2 hours reports: junit: warnings.xml @@ -162,10 +167,12 @@ build-dac: - mv libgdx-static.a libgdx-macos-dac.a - lipo -create libgdxcclib64_deg.dylib libgdxcclib64_dac.dylib -output libgdxcclib64.dylib - lipo -create libgdx-macos-deg.a libgdx-macos-dac.a -output libgdx-macos.a + - mkdir -p ./build + - mv {gdxdump,gdxdiff,gdxmerge} ./build needs: [fetch-ci-scripts,apigenerator,build-deg] artifacts: name: gdx-dac - paths: [gdxtest,gdxwraptest,libgdxcclib64.dylib,libgdx-macos.a] + paths: [gdxtest,gdxwraptest,libgdxcclib64.dylib,libgdx-macos.a,build] expire_in: 2 hours reports: junit: warnings.xml @@ -192,14 +199,14 @@ build-wei: #======================================================================================================================= -codechecker-leg: - extends: .codechecker-leg +codechecker-leg-debug: + extends: .codechecker-leg-debug needs: [fetch-ci-scripts,apigenerator] script: - !reference [.get-gams] - !reference [.gams-folder-leg] - cp apifiles/* src/ - - !reference [.default-codecheck-script, script] + - !reference [.default-codecheck-script-debug, script] #======================================================================================================================= @@ -278,6 +285,89 @@ test-wei: #======================================================================================================================= +# test-gdxtools-leg: +# stage: test-gdxtools +# tags: [linux] +# image: +# name: $MACHINES_CONTAINER_REG/leg/builder-full:latest +# entrypoint: [""] +# needs: [fetch-ci-scripts,build-leg] +# script: +# - !reference [ .get-gams ] +# - !reference [ .gams-folder-leg ] +# - GAMS_PATH=/cache/gams-installs/`cat gams_folder_leg.txt`/ +# - PATH="$GAMS_PATH:$PATH" +# - mkdir -p ~/miniconda3 +# - curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -o ~/miniconda3/miniconda.sh +# - bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3 +# - rm -rf ~/miniconda3/miniconda.sh +# - ~/miniconda3/bin/conda init bash +# - source ~/.bashrc +# - ./src/tools/tests/environments/conda/install.sh +# - conda activate gdxtools +# - python3 ./src/tools/tests/main.py + +# test-gdxtools-deg: +# stage: test-gdxtools +# tags: [macos] +# needs: [fetch-ci-scripts,build-deg] +# script: +# - !reference [ .get-gams ] +# - !reference [ .gams-folder-deg ] +# - GAMS_PATH=$HOME/cache/gams-installs/`cat gams_folder_deg.txt` +# - PATH="$GAMS_PATH:$PATH" +# - mkdir -p ~/miniconda3 +# - curl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -o ~/miniconda3/miniconda.sh +# - bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3 +# - rm -rf ~/miniconda3/miniconda.sh +# - ~/miniconda3/bin/conda init bash +# - source ~/.bashrc +# - ./src/tools/tests/environments/conda/install.sh +# - conda activate gdxtools +# - python3 ./src/tools/tests/main.py + +# test-gdxtools-dac: +# stage: test-gdxtools +# tags: [macos-arm64] +# needs: [fetch-ci-scripts,build-dac] +# script: +# - !reference [ .get-gams ] +# - !reference [ .gams-folder-dac ] +# - GAMS_PATH=$HOME/cache/gams-installs/`cat gams_folder_dac.txt` +# - PATH="$GAMS_PATH:$PATH" +# - mkdir -p ~/miniconda3 +# - curl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh -o ~/miniconda3/miniconda.sh +# - bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3 +# - rm -rf ~/miniconda3/miniconda.sh +# - ~/miniconda3/bin/conda init bash +# - source ~/.bashrc +# - ./src/tools/tests/environments/conda/install.sh +# - conda activate gdxtools +# - python3 ./src/tools/tests/main.py + +# test-gdxtools-wei: +# stage: test-gdxtools +# tags: [windows] +# image: +# name: $MACHINES_CONTAINER_REG/wei/builder-full:latest +# needs: [fetch-ci-scripts,build-wei] +# script: +# - !reference [ .get-gams-wei ] +# - !reference [ .gams-folder-wei ] +# - $gmsdirname = Get-Content mygmsdir.tmp -Raw +# - $GAMS_PATH = "C:\Cache\gams-installs\$gmsdirname" +# - $env:Path = "$GAMS_PATH;$GAMS_PATH\gbin;" + $env:Path +# # - $env:GAMSDIR = "$GAMS_PATH;$GAMS_PATH\gbin" +# - curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe -o miniconda.exe +# - start /wait "" miniconda.exe /S +# - del miniconda.exe +# # - Update-SessionEnvironment +# - .\src\tools\tests\environments\conda\install.sh +# - conda activate gdxtools +# - python3 .\src\tools\tests\main.py + +#======================================================================================================================= + leak-check-leg: stage: leak-check tags: [linux] diff --git a/CMakeLists.txt b/CMakeLists.txt index ffe2e61..f1ce784 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,97 +54,97 @@ endif (UNIX) include(base-units.cmake) set(zlib-units - # ZLIB modules - # missing? run in project root: git clone https://github.com/madler/zlib zlib - zlib/adler32.c zlib/compress.c zlib/crc32.c zlib/deflate.c zlib/gzclose.c zlib/gzlib.c zlib/gzread.c - zlib/gzwrite.c zlib/infback.c zlib/inffast.c zlib/inflate.c zlib/inftrees.c zlib/trees.c zlib/uncompr.c - zlib/zutil.c + # ZLIB modules + # missing? run in project root: git clone https://github.com/madler/zlib zlib + zlib/adler32.c zlib/compress.c zlib/crc32.c zlib/deflate.c zlib/gzclose.c zlib/gzlib.c zlib/gzread.c + zlib/gzwrite.c zlib/infback.c zlib/inffast.c zlib/inflate.c zlib/inftrees.c zlib/trees.c zlib/uncompr.c + zlib/zutil.c ) set(gdx-core - # P3 runtime library - src/rtl/dtoaLoc.h - src/rtl/dtoaLoc.c - src/rtl/p3io.h - src/rtl/p3io.cpp - src/rtl/p3utils.h - src/rtl/p3utils.cpp - src/rtl/sysutils_p3.h - src/rtl/sysutils_p3.cpp - src/rtl/p3platform.h - src/rtl/p3platform.cpp - src/rtl/math_p3.h - src/rtl/math_p3.cpp - - # gdlib essential units - src/gdlib/gmsstrm.cpp - src/gdlib/gmsstrm.h - src/gdlib/utils.h - src/gdlib/utils.cpp - src/gdlib/strutilx.h - src/gdlib/strutilx.cpp - src/gdlib/strindexbuf.h - - # GDX specific units - src/gxfile.h - src/gxfile.cpp - src/gdx.h - - ${zlib-units} + # P3 runtime library + src/rtl/dtoaLoc.h + src/rtl/dtoaLoc.c + src/rtl/p3io.h + src/rtl/p3io.cpp + src/rtl/p3utils.h + src/rtl/p3utils.cpp + src/rtl/sysutils_p3.h + src/rtl/sysutils_p3.cpp + src/rtl/p3platform.h + src/rtl/p3platform.cpp + src/rtl/math_p3.h + src/rtl/math_p3.cpp + + # gdlib essential units + src/gdlib/gmsstrm.cpp + src/gdlib/gmsstrm.h + src/gdlib/utils.h + src/gdlib/utils.cpp + src/gdlib/strutilx.h + src/gdlib/strutilx.cpp + src/gdlib/strindexbuf.h + + # GDX specific units + src/gxfile.h + src/gxfile.cpp + src/gdx.h + + ${zlib-units} ) set(test-deps - # runtime library - src/rtl/p3library.cpp - src/rtl/p3library.h - src/rtl/stdthread.cpp - src/rtl/stdthread.h - - # global - src/global/gmslibname.cpp - src/global/gmslibname.h - - # gdlib - src/gdlib/charmaps.cpp - src/gdlib/charmaps.h - src/gdlib/gmacro.cpp - src/gdlib/gmacro.h - src/gdlib/gmsheapnew.cpp - src/gdlib/gmsheapnew.h - src/gdlib/gmsonly.h - src/gdlib/gmsonly.cpp - src/gdlib/obfuscatestr.cpp - src/gdlib/obfuscatestr.h - src/gdlib/strhash.h - src/gdlib/strhash.cpp - src/gdlib/xcompress.cpp - src/gdlib/xcompress.h + # runtime library + src/rtl/p3library.cpp + src/rtl/p3library.h + src/rtl/stdthread.cpp + src/rtl/stdthread.h + + # global + src/global/gmslibname.cpp + src/global/gmslibname.h + + # gdlib + src/gdlib/charmaps.cpp + src/gdlib/charmaps.h + src/gdlib/gmacro.cpp + src/gdlib/gmacro.h + src/gdlib/gmsheapnew.cpp + src/gdlib/gmsheapnew.h + src/gdlib/gmsonly.h + src/gdlib/gmsonly.cpp + src/gdlib/obfuscatestr.cpp + src/gdlib/obfuscatestr.h + src/gdlib/strhash.h + src/gdlib/strhash.cpp + src/gdlib/xcompress.cpp + src/gdlib/xcompress.h ) set(tests - src/tests/doctestmain.cpp - - src/tests/gdxtests.h - src/tests/gdxtests.cpp - src/tests/gxfiletests.cpp - - src/tests/gdlib/datastoragetests.cpp - src/tests/gdlib/glookuptests.cpp - src/tests/gdlib/gmacrotests.cpp - src/tests/gdlib/gmsdatatests.cpp - src/tests/gdlib/gmsheapnewtests.cpp - src/tests/gdlib/gmsobjtests.cpp - src/tests/gdlib/gmsstrmtests.cpp - src/tests/gdlib/obfuscatestrtests.cpp - src/tests/gdlib/strhashtests.cpp - src/tests/gdlib/strutilxtests.cpp - src/tests/gdlib/utilstests.cpp - src/tests/gdlib/xcompresstests.cpp - - src/tests/rtl/p3iotests.cpp - src/tests/rtl/p3utilstests.cpp - src/tests/rtl/stdthreadtests.cpp - src/tests/rtl/sysutilsp3tests.cpp + src/tests/doctestmain.cpp + + src/tests/gdxtests.h + src/tests/gdxtests.cpp + src/tests/gxfiletests.cpp + + src/tests/gdlib/datastoragetests.cpp + src/tests/gdlib/glookuptests.cpp + src/tests/gdlib/gmacrotests.cpp + src/tests/gdlib/gmsdatatests.cpp + src/tests/gdlib/gmsheapnewtests.cpp + src/tests/gdlib/gmsobjtests.cpp + src/tests/gdlib/gmsstrmtests.cpp + src/tests/gdlib/obfuscatestrtests.cpp + src/tests/gdlib/strhashtests.cpp + src/tests/gdlib/strutilxtests.cpp + src/tests/gdlib/utilstests.cpp + src/tests/gdlib/xcompresstests.cpp + + src/tests/rtl/p3iotests.cpp + src/tests/rtl/p3utilstests.cpp + src/tests/rtl/stdthreadtests.cpp + src/tests/rtl/sysutilsp3tests.cpp ) set(inc-dirs zlib src generated) diff --git a/README.md b/README.md index 7440e40..b3812e3 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ For an easier to use object-oriented interface GAMS offers wrappers for multiple Even more abstraction is offered by the GAMS Transfer libraries for [Python](https://www.gams.com/latest/docs/API_PY_GAMSTRANSFER.html) and -[R](https://www.gams.com/latest/docs/API_R_GAMSTRANSFER.html). +[R](https://transfer-r.readthedocs.io/en/stable). ### Building GDX from source @@ -124,7 +124,7 @@ The fastest way to build the GDX library (static and dynamic) locally is by runn git clone https://github.com/GAMS-dev/gdx.git cd gdx git clone https://github.com/madler/zlib zlib -cmake -DNO_TESTS=ON -DNO_EXAMPLES=ON . +cmake -DNO_TESTS=ON -DNO_EXAMPLES=ON -DNO_TOOLS=ON . cmake --build . ``` Running this on Linux creates the dynamic library `libgdxcclib64.so` and the static library `libgdx-static.a`. @@ -1053,14 +1053,14 @@ static gdxValues_t Values; void ReportGDXError(gdxHandle_t PGX) { char S[GMS_SSSIZE]; - std::cout << "**** Fatal GDX Error" << endl; + cout << "**** Fatal GDX Error" << endl; gdxErrorStr(PGX, gdxGetLastError(PGX), S); - std::cout << "**** " << S << endl; + cout << "**** " << S << endl; exit(1); } -void ReportIOError(int N, const std::string &msg) { - std::cout << "**** Fatal I/O Error = " << N << " when calling " << msg << endl; +void ReportIOError(int N, const string &msg) { + cout << "**** Fatal I/O Error = " << N << " when calling " << msg << endl; exit(1); } diff --git a/changelog.yaml b/changelog.yaml index bf8176a..34eb10d 100644 --- a/changelog.yaml +++ b/changelog.yaml @@ -1,4 +1,6 @@ --- +- 7.11.7: + - Added C++ port of GDX utilities gdxdiff, gdxdump, and gdxmerge. - 7.11.7: - Fix building GDX on arm64 with GCC (char is unsigned by default there). - Multiple cosmetic changes. diff --git a/src/gdlib/charmaps.cpp b/src/gdlib/charmaps.cpp index 36adcd5..b657add 100644 --- a/src/gdlib/charmaps.cpp +++ b/src/gdlib/charmaps.cpp @@ -78,33 +78,37 @@ constexpr std::array mapToSelf { void InitChars( const bool AllChars ) { + using uchar = unsigned char; static_assert( mapcharBuf.size() == 256 ); if( AllChars ) { - for( unsigned char c = std::numeric_limits::min(); c < std::numeric_limits::max(); c++ ) + for( unsigned char c = std::numeric_limits::min(); c < std::numeric_limits::max(); c++ ) mapcharBuf[c] = static_cast(c); return; } // everything not explicitly whitelisted afterward kills the compilation (or is it ignored?)? - for( unsigned char c = std::numeric_limits::min(); c <= std::numeric_limits::max(); c++ ) - mapcharBuf[c] = std::char_traits::eof(); + for( uchar c = std::numeric_limits::min(); c <= std::numeric_limits::max(); c++ ) + mapcharBuf[c] = chareof; - for(char c {'A'}; c <= 'Z'; c++) + for( char c { 'A' }; c <= 'Z'; c++ ) if(letter[c]) mapcharBuf[c] = c; - for(char c {'a'}; c <= 'z'; c++) + for( char c { 'a' }; c <= 'z'; c++ ) if(letter[c]) mapcharBuf[c] = c; - for(char c {'0'}; c <= '9'; c++) + for( char c { '0' }; c <= '9'; c++ ) if(digit[c]) mapcharBuf[c] = c; - for( const char c : mapToSelf ) - mapcharBuf[c] = c; + for( const uchar c: mapToSelf ) + { + assert( c <= std::numeric_limits::max() ); + mapcharBuf[c] = static_cast(c); + } } void InitCharacterMaps() diff --git a/src/gdlib/charmaps.h b/src/gdlib/charmaps.h index 98c64cc..1fbb9b6 100644 --- a/src/gdlib/charmaps.h +++ b/src/gdlib/charmaps.h @@ -38,12 +38,12 @@ namespace gdlib::charmaps { -constexpr char charff = '\f', - charcr = '\r', - chareof = std::char_traits::eof(), - chareol = '\0', - charlf = '\n', - chartab = '\t', +constexpr char charff = '\f', // 0x0C + charcr = '\r', // 0x0D + chareof = 0x1A, // note: != std::char_traits::eof(), + chareol = '\0', // 0x00 + charlf = '\n', // 0x0A + chartab = '\t', // 0x09 char200 = static_cast( 200 ); extern utils::charset digit, @@ -62,6 +62,7 @@ constexpr int numCharVals {std::numeric_limits::max()+1}; extern std::array mapcharBuf; inline char mapchar( const char c) { + assert( c >= 0 && static_cast(c) <= 255 ); return mapcharBuf[static_cast( c )]; } diff --git a/src/gdlib/dblutil.cpp b/src/gdlib/dblutil.cpp index cb9afef..d31f4af 100644 --- a/src/gdlib/dblutil.cpp +++ b/src/gdlib/dblutil.cpp @@ -46,42 +46,66 @@ double gdRoundTo( const double x, const int i ) return std::trunc( x * zReciprocal + 0.5 * ( x > 0.0 ? 1.0 : -1.0 ) ) / zReciprocal; } +constexpr TI64Rec t64 { 1 }; +const bool bigEndian { t64.bytes.back() == 1 }; + constexpr int64_t signMask { static_cast( 0x80000000 ) << 32 }, - expoMask { static_cast( 0x7ff00000 ) << 32 }, - mantMask { ~( signMask | expoMask ) }; + expoMask { static_cast( 0x7ff00000 ) << 32 }, + mantMask { ~( signMask | expoMask ) }; -union TI64Rec -{ - double x; - int64_t i64; -// uint8_t bytes[8]; -}; -static void dblDecomp( const double x, bool& isNeg, uint32_t& expo, int64_t& mant) +static void dblDecomp( const double x, bool &isNeg, uint32_t &expo, int64_t &mant ) { - TI64Rec xi {}; - xi.x = x; + TI64Rec xi { x }; isNeg = ( xi.i64 & signMask ) == signMask; expo = ( xi.i64 & expoMask ) >> 52; mant = xi.i64 & mantMask; } -static char hexDigit( const uint8_t b) { - return static_cast(b < 10 ? utils::ord('0') + b : utils::ord('a') + b - 10); +char hexDigit( const uint8_t b ) +{ + return static_cast( b < 10 ? utils::ord( '0' ) + b : utils::ord( 'a' ) + b - 10 ); +} + +std::string dblToStrHex( const double x ) +{ + TI64Rec xi { x }; + uint8_t c; + std::string result = "0x"; + + if( bigEndian ) + { + for( int i {}; i < 8; i++ ) + { + c = xi.bytes[i]; + result += gdlib::dblutil::hexDigit( c / 16 ); + result += gdlib::dblutil::hexDigit( c & 0x0F ); + } + } + else + { + for( int i { 7 }; i >= 0; i-- ) + { + c = xi.bytes[i]; + result += gdlib::dblutil::hexDigit( c / 16 ); + result += gdlib::dblutil::hexDigit( c & 0x0F ); + } + } + return result; } // format the bytes in the mantissa -static std::string mFormat(int64_t m) { - if (!m) +static std::string mFormat( int64_t m ) +{ + if( !m ) return "0"s; - //TI64Rec xi {}; - //xi.i64 = m; int64_t mask { static_cast( 0x000f0000 ) << 32 }; int shiftCount = 48; std::string res; - while (m) { + while( m ) + { const int64_t m2 { ( m & mask ) >> shiftCount }; - const auto b { static_cast(m2) }; + const auto b { static_cast( m2 ) }; res += hexDigit( b ); m &= ~mask; mask >>= 4; @@ -124,4 +148,4 @@ std::string dblToStrHexponential( const double x ) } -}// namespace gdlib::dblutil \ No newline at end of file +}// namespace gdlib::dblutil diff --git a/src/gdlib/dblutil.h b/src/gdlib/dblutil.h index a95293e..af5409a 100644 --- a/src/gdlib/dblutil.h +++ b/src/gdlib/dblutil.h @@ -26,12 +26,26 @@ #pragma once +#include #include +#include namespace gdlib::dblutil { +union TI64Rec +{ + double x; + int64_t i64; + std::array bytes; +}; + double gdRoundTo( double x, int i ); + +char hexDigit( uint8_t b ); + +std::string dblToStrHex( double x ); + std::string dblToStrHexponential( double x ); -} \ No newline at end of file +}// namespace gdlib::dblutil diff --git a/src/gdlib/glookup.h b/src/gdlib/glookup.h index 818b471..97bb7db 100644 --- a/src/gdlib/glookup.h +++ b/src/gdlib/glookup.h @@ -199,7 +199,7 @@ class TGAMSRecList { assert( !HashTable && "StoreEntry" ); RecList.push_back( PRec ); - return RecList.size(); + return static_cast(RecList.size()); } int MemoryUsed() { return RecList.size() * sizeof( T ); } @@ -290,7 +290,7 @@ class TGAMSRecList if( FSrtCount != static_cast( RecList.size() ) ) { if( FSrtCount > 0 ) FSrtIndx = nullptr; - FSrtCount = RecList.size(); + FSrtCount = static_cast(RecList.size()); FSrtIndx = std::make_unique>( FSrtCount ); } PA = FSrtIndx->data();// local copy for speed @@ -336,12 +336,12 @@ class TGAMSRecList [[nodiscard]] int GetCount() const { - return RecList.size(); + return static_cast(RecList.size()); } [[nodiscard]] int Count() const { - return RecList.size(); + return static_cast(RecList.size()); } }; diff --git a/src/gdlib/gmslist.cpp b/src/gdlib/gmslist.cpp index dfb77ba..e669d4c 100644 --- a/src/gdlib/gmslist.cpp +++ b/src/gdlib/gmslist.cpp @@ -395,14 +395,31 @@ void TGmsList::CheckIndxQue() } } -void TGmsList::SysStrWrite( std::string_view s ) +void TGmsList::SysStrWrite( const std::string_view s ) { if( !PFile ) return; - utils::fputstr( PFile, ( CaseAction == casToUpper ? strutilx::UpperCase( s ) : ( CaseAction == casToLower ? strutilx::LowerCase( s ) : s ) ) ); + switch( CaseAction ) + { + case casToLower: + { + const auto ls = strutilx::UpperCase( s ); + utils::fputstr( PFile, ls ); + break; + } + case casToUpper: + { + const auto us = strutilx::UpperCase( s ); + utils::fputstr( PFile, us ); + break; + } + case casNone: + utils::fputstr( PFile, s ); + break; + } FCharsWritten += static_cast( s.length() ); } -void TGmsList::SysStrWrite( const char *s, size_t slen ) +void TGmsList::SysStrWrite( const char *s, const size_t slen ) { if( !PFile ) return; fwrite( s, sizeof( char ), slen, PFile ); diff --git a/src/gdlib/gmsobj.h b/src/gdlib/gmsobj.h index 5b64f64..8bcd9ae 100644 --- a/src/gdlib/gmsobj.h +++ b/src/gdlib/gmsobj.h @@ -118,7 +118,7 @@ class TXList return res; } - void Clear() + virtual void Clear() { for( int N { FCount - 1 + ( OneBased ? 1 : 0 ) }; N >= ( OneBased ? 1 : 0 ); N-- ) FreeItem( N ); FCount = 0; @@ -505,6 +505,7 @@ class TXCustomStringList : public TQuickSortClass if( OneBased ) Index--; if( Index < FCount )// overlap so use memmove instead of memcpy std::memmove( &FList[Index + 1], &FList[Index], ( FCount - Index ) * sizeof( TStringItem ) ); + assert(FList); FList[Index].FString = NewString( S, slen, FStrMemory ); FList[Index].FObject = APointer; FCount++; @@ -740,6 +741,7 @@ class TXHashedStringList : public TXCustomStringList if( !pHashSC || this->FCount > trigger ) SetHashSize( this->FCount ); const auto hv { hashValue( s, slen ) }; PHashRecord PH; + assert(pHashSC); for( PH = pHashSC[hv]; PH && !compareEntry( s, PH->RefNr ); PH = PH->PNext ) ; if( PH ) return PH->RefNr + ( this->OneBased ? 1 : 0 ); diff --git a/src/gdlib/gmsstrm.cpp b/src/gdlib/gmsstrm.cpp index a3ccca5..dd16cba 100644 --- a/src/gdlib/gmsstrm.cpp +++ b/src/gdlib/gmsstrm.cpp @@ -83,7 +83,7 @@ constexpr uint8_t signature_header = 0xFF; const std::string signature_gams = "*GAMS*"s; constexpr int verify_offset = 100; -constexpr static char substChar { std::numeric_limits::is_signed ? std::char_traits::eof() : 0x1A }; +constexpr static char substChar { 0x1A }; union TDoubleVar { @@ -1243,7 +1243,7 @@ void TBinaryTextFileIO::ReadLine( std::string &StrBuffer, int &Len, const int Ma return; } StrBuffer.clear(); - while( !( utils::in( LastChar, substChar, '\n', '\r', static_cast(0x1A) ) || static_cast( StrBuffer.size() ) == MaxInp ) ) + while( !( utils::in( LastChar, substChar, '\n', '\r' ) || static_cast( StrBuffer.size() ) == MaxInp ) ) { StrBuffer.push_back( LastChar ); // the simple case diff --git a/src/gdlib/strutilx.cpp b/src/gdlib/strutilx.cpp index 02e7948..df965e1 100644 --- a/src/gdlib/strutilx.cpp +++ b/src/gdlib/strutilx.cpp @@ -176,7 +176,7 @@ std::string PadRightMod( std::string_view s, const int M ) // Sp: Starting position // Returns: // Location of the character when found; -1 otherwise -static int LChPosSp( const char Ch, const char *S, int Sp ) +int LChPosSp( const char Ch, const char *S, int Sp ) { if( Sp < 0 ) Sp = 0; for( int K { Sp }; S[K]; K++ ) @@ -184,11 +184,30 @@ static int LChPosSp( const char Ch, const char *S, int Sp ) return -1; } -static int LChPos( const char Ch, const char *S ) +int LChPos( const char Ch, const char *S ) { return LChPosSp( Ch, S, 0 ); } +// Brief: +// Search for a set of characters from the left +// Arguments: +// Cs: Character set to search +// S: String to be searched +// Returns: +// Location of the character when found; -1 otherwise +int LChSetPos( const char *Cs, const char *S, const int slen ) +{ + const char *c { Cs }; + for( int k { 0 }; k <= slen - 1; k++ ) + { + while( *c ) + if( *c++ == S[k] ) return k; + c = Cs; + } + return -1; +} + // Brief: // Search for a set of characters from the right // Arguments: @@ -196,7 +215,7 @@ static int LChPos( const char Ch, const char *S ) // S: String to be searched // Returns: // Location of the character when found; -1 otherwise -static int RChSetPos( const char *Cs, const char *S, const int slen ) +int RChSetPos( const char *Cs, const char *S, const int slen ) { const char *c {Cs}; for( int k {slen-1}; k >= 0; k-- ) diff --git a/src/gdlib/strutilx.h b/src/gdlib/strutilx.h index 9041746..95362ef 100644 --- a/src/gdlib/strutilx.h +++ b/src/gdlib/strutilx.h @@ -64,9 +64,14 @@ std::string ExcelColStr( int C ); int IntegerWidth( int n ); int PadModLength( std::string_view s, int M ); - std::string PadRightMod( std::string_view s, int M ); +int LChPosSp( char Ch, const char *S, int Sp ); +int LChPos( char Ch, const char *S ); + +int LChSetPos( const char *Cs, const char *S, int slen ); +int RChSetPos( const char *Cs, const char *S, int slen ); + std::string DblToStrSep( double V, char DecimalSep ); uint8_t DblToStrSep( double V, char DecimalSep, char *s ); diff --git a/src/gdlib/utils.cpp b/src/gdlib/utils.cpp index 02b459a..20695a2 100644 --- a/src/gdlib/utils.cpp +++ b/src/gdlib/utils.cpp @@ -138,11 +138,6 @@ std::string getLineWithSep( std::istream &fs ) return line; } -bool sameTextAsAny( const std::string_view a, const std::initializer_list &bs ) -{ - return any( [&a]( const std::string_view b ) { return sameText( a, b ); }, bs ); -} - bool sameTextPrefix( const std::string_view s, const std::string_view prefix ) { return sameText( s.substr( 0, prefix.length() ), prefix ); @@ -589,8 +584,6 @@ void stocp( const std::string &s, char *cp ) std::memcpy( cp, s.c_str(), s.length() + 1 ); } -inline int b2i( bool b ) { return b ? 1 : 0; } - int strCompare( const std::string_view S1, const std::string_view S2, const bool caseInsensitive ) { if( S1.empty() || S2.empty() ) return b2i( !S1.empty() ) - b2i( !S2.empty() ); @@ -606,7 +599,7 @@ int strCompare( const std::string_view S1, const std::string_view S2, const bool return static_cast( S1.length() - S2.length() ); } -StringBuffer::StringBuffer( int size ) : s( size, '\0' ), bufferSize { size } {} +StringBuffer::StringBuffer( const int size ) : s( size, '\0' ), bufferSize { size } {} char *StringBuffer::getPtr() { return &s[0]; } diff --git a/src/gdlib/utils.h b/src/gdlib/utils.h index c0c1de1..84318c2 100644 --- a/src/gdlib/utils.h +++ b/src/gdlib/utils.h @@ -162,13 +162,13 @@ class charset { public: charset(const std::initializer_list cs) { - for(char c : cs) + for( const char c : cs) insert(c); } charset(const charset &other) = default; charset() = default; - void insert(char c) { + void insert( const char c) { chars.set(c+offset); } @@ -190,7 +190,7 @@ class charset { chars.reset(); } - void erase(char c) { + void erase( const char c) { chars.reset(c+offset); } }; @@ -458,7 +458,17 @@ bool sameText( const std::string_view a, const std::string_view b ) return caseInvariant ? sameTextInvariant( a, b ) : a == b; } -bool sameTextAsAny( std::string_view a, const std::initializer_list &bs ); +inline bool sameTextAsAny(std::string_view a, std::string_view b) +{ + return sameText( a, b ); +} + +template +bool sameTextAsAny( std::string_view str1, std::string_view str2, Args... args ) +{ + return sameText( str1, str2 ) || sameTextAsAny( str1, args... ); +} + bool sameTextPrefix( std::string_view s, std::string_view prefix ); // Port of PStr(U)Equal @@ -743,6 +753,8 @@ inline auto ord( const char c ) return static_cast( c ); } +inline auto b2i(const bool x) { return x ? 1 : 0; } + inline int pos( const char c, const std::string &s ) { const auto p { s.find( c ) }; diff --git a/src/global/ctvproc.h b/src/global/ctvproc.h index 7781c1a..17be6b0 100644 --- a/src/global/ctvproc.h +++ b/src/global/ctvproc.h @@ -27,7 +27,6 @@ #include #include -#include #include "../gdlib/utils.h" @@ -57,19 +56,19 @@ enum tprocname : uint8_t proccount }; -const int procnameslength = 16; -const std::array procnames = { +constexpr int procnameslength { 16 }; +const std::array procnames { "NONE", "LP", "MIP", "RMIP", "NLP", "MCP", "MPEC", "RMPEC", "CNS", "DNLP", "RMINLP", "MINLP", "QCP", "MIQCP", "RMIQCP", "EMP" }; -const utils::bsSet pshortform = { procmcp, proccns }, - pdiscrete = { procmip, procrmip, procmpec, procrmpec, procrminlp, procminlp, procmiqcp, procrmiqcp, procemp }, - pnonlinear = { procnlp, procmcp, procmpec, procrmpec, proccns, procdnlp, procrminlp, procminlp, procqcp, procmiqcp, procrmiqcp, procemp }, - pcontinuous = { procnlp, procmcp, procmpec, proccns, procrminlp, procminlp, procqcp, procmiqcp, procrmiqcp, procemp }, - pdiscontinuous = { procdnlp, procrminlp, procminlp, procemp }, - pdrelaxed = { procrmip, procrmpec, procrminlp, procrmiqcp }, - pdtrylinear = { procnlp, procdnlp, procrminlp, procminlp, procqcp, procmiqcp, procrmiqcp }, - pdskiplist = { procnone }, - pdpairs = { procmcp, procmpec, procrmpec, proccns, procemp }; +const utils::bsSet pshortform { procmcp, proccns }, + pdiscrete { procmip, procrmip, procmpec, procrmpec, procrminlp, procminlp, procmiqcp, procrmiqcp, procemp }, + pnonlinear { procnlp, procmcp, procmpec, procrmpec, proccns, procdnlp, procrminlp, procminlp, procqcp, procmiqcp, procrmiqcp, procemp }, + pcontinuous { procnlp, procmcp, procmpec, proccns, procrminlp, procminlp, procqcp, procmiqcp, procrmiqcp, procemp }, + pdiscontinuous { procdnlp, procrminlp, procminlp, procemp }, + pdrelaxed { procrmip, procrmpec, procrminlp, procrmiqcp }, + pdtrylinear { procnlp, procdnlp, procrminlp, procminlp, procqcp, procmiqcp, procrmiqcp }, + pdskiplist { procnone }, + pdpairs { procmcp, procmpec, procrmpec, proccns, procemp }; const std::array proctexts { "NONE" , diff --git a/src/gxfile.cpp b/src/gxfile.cpp index cef4a33..a1e8e36 100644 --- a/src/gxfile.cpp +++ b/src/gxfile.cpp @@ -1296,7 +1296,7 @@ bool TGXFileObj::DoWrite( const int *AElements, const double *AVals ) } if( FDim == FCurrentDim && delta <= DeltaForWrite ) {// small change in last dimension - assert(FCurrentDim >= 0 && FCurrentDim <= 255); + assert(FCurrentDim >= 1 && FCurrentDim < 21); FFile->WriteByte( utils::ui8(FCurrentDim + delta) ); LastElem[FCurrentDim - 1] = AElements[FCurrentDim - 1]; } @@ -1306,6 +1306,7 @@ bool TGXFileObj::DoWrite( const int *AElements, const double *AVals ) FFile->WriteByte( static_cast(FDim) ); for( int D { FDim - 1 }; D < FCurrentDim; D++ ) { + assert( D >= 0 && D < 20 ); const int v { AElements[D] - MinElem[D] }; switch( ElemType[D] ) { diff --git a/src/rtl/idglobal_p3.cpp b/src/rtl/idglobal_p3.cpp index 616aee8..0b9bc30 100644 --- a/src/rtl/idglobal_p3.cpp +++ b/src/rtl/idglobal_p3.cpp @@ -45,6 +45,10 @@ uint32_t GetTickDiff( const uint32_t AOldTickCount, const uint32_t ANewTickCount uint32_t GetTickCount() { #if defined( _WIN32 ) + // Consider using 'GetTickCount64' instead of 'GetTickCount'. + // Reason: GetTickCount overflows roughly every 49 days. + // Code that does not take that into account can loop indefinitely. + // GetTickCount64 operates on 64 bit values and does not have that problem return ::GetTickCount(); #else timeval tv {}; diff --git a/src/rtl/p3utils.cpp b/src/rtl/p3utils.cpp index 89c7c0f..d9b4324 100644 --- a/src/rtl/p3utils.cpp +++ b/src/rtl/p3utils.cpp @@ -49,6 +49,7 @@ //#define _WINSOCK2API_ #define _WINSOCKAPI_ /* Prevent inclusion of winsock.h in windows.h */ #include + #include #endif #include #include @@ -1502,8 +1503,9 @@ T_P3SOCKET p3SockCreateConnectedClient( int port ) SOCKADDR_IN addr {}; std::memset( &addr, 0, sizeof( addr ) ); addr.sin_family = AF_INET; + //addr.sin_addr.s_addr = inet_addr( "127.0.0.1" ); + InetPtonA( AF_INET, "127.0.0.1", &addr ); addr.sin_port = htons( port ); - addr.sin_addr.s_addr = inet_addr( "127.0.0.1" ); if( connect( s, reinterpret_cast( &addr ), sizeof( addr ) ) == SOCKET_ERROR ) return res; res.wsocket = s; @@ -1748,8 +1750,9 @@ T_P3SOCKET p3SockCreateServerSocket( int port, bool reuse ) SOCKADDR_IN addr; (void) std::memset( &addr, 0, sizeof( addr ) ); addr.sin_family = AF_INET; + //addr.sin_addr.s_addr = inet_addr( "127.0.0.1" ); + InetPtonA( AF_INET, "127.0.0.1", &addr ); addr.sin_port = htons( port ); - addr.sin_addr.s_addr = inet_addr( "127.0.0.1" ); int rc { bind( acceptSocket, (SOCKADDR *) &addr, sizeof( addr ) ) }; if( rc == SOCKET_ERROR ) { diff --git a/src/rtl/sysutils_p3.h b/src/rtl/sysutils_p3.h index 39eedfc..fdd16b3 100644 --- a/src/rtl/sysutils_p3.h +++ b/src/rtl/sysutils_p3.h @@ -135,9 +135,9 @@ bool RemoveDir(const std::string &Dir); using TFileName = std::string; struct TSearchRec { - int Time, Size, Attr; + int Time {}, Size {}, Attr {}; TFileName Name; - int ExcludeAttr; + int ExcludeAttr {}; #if defined(_WIN32) HANDLE FindHandle {}; _WIN32_FIND_DATAA * FindData {}; @@ -145,7 +145,7 @@ struct TSearchRec { DIR *FindHandle {}; #endif std::string PathOnly, Pattern; - uint32_t mode; + uint32_t mode {}; ~TSearchRec(); }; diff --git a/src/tests/gdlib/utilstests.cpp b/src/tests/gdlib/utilstests.cpp index 588323c..6b781b3 100644 --- a/src/tests/gdlib/utilstests.cpp +++ b/src/tests/gdlib/utilstests.cpp @@ -393,9 +393,9 @@ TEST_CASE( "Test upper-/lowercasing of strings (copy)" ) TEST_CASE( "Test checking if a string case-insensitive matches at least one element from a string list" ) { - REQUIRE_FALSE( utils::sameTextAsAny( "test", { "abc", "aBc", "test X" } ) ); - REQUIRE( utils::sameTextAsAny( "test", { "abc", "TEST" } ) ); - REQUIRE_FALSE( utils::sameTextAsAny( "", {} ) ); + REQUIRE_FALSE( utils::sameTextAsAny( "test", "abc", "aBc", "test X" ) ); + REQUIRE( utils::sameTextAsAny( "test", "abc", "TEST" ) ); + REQUIRE( utils::sameTextAsAny( "test"s, "abc"s, "TEST"s ) ); } TEST_CASE( "Test if a string starts with a prefix" ) diff --git a/src/tools/gdxdiff/gdxdiff.cpp b/src/tools/gdxdiff/gdxdiff.cpp new file mode 100644 index 0000000..e52dc47 --- /dev/null +++ b/src/tools/gdxdiff/gdxdiff.cpp @@ -0,0 +1,1243 @@ +/** + * GAMS - General Algebraic Modeling System C++ API + * + * Copyright (c) 2017-2024 GAMS Software GmbH + * Copyright (c) 2017-2024 GAMS Development Corp. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gdxdiff.h" +#include "../library/cmdpar.h" +#include "../../gdlib/strutilx.h" +#include "../../gdlib/strhash.h" +#include "../../gdlib/gmsobj.h" +#include "../../rtl/sysutils_p3.h" +#include "../../rtl/p3process.h" + +// Global constants +#include "../../../generated/gclgms.h" + +// Increase value to use +#define VERBOSE 0 + +namespace gdxdiff +{ + +using tvarvaltype = unsigned int; + +library::short_string DiffTmpName; +gdxHandle_t PGX1, PGX2, PGXDIF; +bool diffUELsRegistered; +// TODO: Use the correct type instead of nullptr type? +std::unique_ptr> UELTable; +int staticUELNum; +double EpsAbsolute, EpsRelative; +std::map StatusTable; +std::unique_ptr CmdParams; +std::set ActiveFields; +// Use FldOnlyVar instead of FldOnly as the variable name +FldOnly FldOnlyVar; +tvarvaltype FldOnlyFld; +bool DiffOnly, CompSetText, matrixFile, ignoreOrder; +std::unique_ptr IDsOnly; +std::unique_ptr SkipIDs; +bool ShowDefRec, CompDomains; + +std::string ValAsString( const gdxHandle_t &PGX, const double V ) +{ + constexpr int WIDTH { 14 }; + library::short_string result; + if( gdxAcronymName( PGX, V, result.data() ) == 0 ) + { + int iSV; + gdxMapValue( PGX, V, &iSV ); + if( iSV != sv_normal ) + return gdlib::strutilx::PadLeft( library::gdxSpecialValuesStr( iSV ), WIDTH ); + else + { + // TODO: Improve this conversion + std::ostringstream oss; + oss << std::fixed << std::setprecision( 5 ) << V; + result = oss.str(); + return gdlib::strutilx::PadLeft( result.string(), WIDTH ); + } + } + // Empty string will be returned + return {}; +} + +void FatalErrorExit( const int ErrNr ) +{ + if( !DiffTmpName.empty() && rtl::sysutils_p3::FileExists( DiffTmpName.string() ) ) + { + if( PGXDIF ) + { + gdxClose( PGXDIF ); + gdxFree( &PGXDIF ); + } + rtl::sysutils_p3::DeleteFileFromDisk( DiffTmpName.string() ); + } + exit( ErrNr ); +} + +void FatalError( const std::string &Msg, const int ErrNr ) +{ + library::printErrorMessage( "GDXDIFF error: " + Msg ); + FatalErrorExit( ErrNr ); +} + +void FatalError2( const std::string &Msg1, const std::string &Msg2, const int ErrNr ) +{ + library::printErrorMessage( "GDXDIFF error: " + Msg1 ); + library::printErrorMessage( " " + Msg2 ); + FatalErrorExit( ErrNr ); +} + +void CheckGDXError( const gdxHandle_t &PGX ) +{ + int ErrNr { gdxGetLastError( PGX ) }; + if( ErrNr != 0 ) + { + library::short_string S; + gdxErrorStr( PGX, ErrNr, S.data() ); + library::printErrorMessage( "GDXDIFF GDX Error: " + S.string() ); + } +} + +void OpenGDX( const library::short_string &fn, gdxHandle_t &PGX ) +{ + if( !rtl::sysutils_p3::FileExists( fn.string() ) ) + FatalError( "Input file not found " + fn.string(), static_cast( ErrorCode::ERR_NOFILE ) ); + + library::short_string S; + if( !gdxCreate( &PGX, S.data(), S.length() ) ) + FatalError( "Cannot load GDX library " + S.string(), static_cast( ErrorCode::ERR_LOADDLL ) ); + + int ErrNr; + gdxOpenRead( PGX, fn.data(), &ErrNr ); + if( ErrNr != 0 ) + { + gdxErrorStr( PGX, ErrNr, S.data() ); + FatalError2( "Problem reading GDX file + " + fn.string(), S.string(), static_cast( ErrorCode::ERR_READGDX ) ); + } + + int NrElem, HighV; + gdxUMUelInfo( PGX, &NrElem, &HighV ); + gdxUELRegisterMapStart( PGX ); + for( int N { 1 }; N <= NrElem; N++ ) + { + int NN; + library::short_string UEL; + gdxUMUelGet( PGX, N, UEL.data(), &NN ); + NN = UELTable->Add( UEL.data(), UEL.length() ); + gdxUELRegisterMap( PGX, NN, UEL.data() ); + } + gdxUELRegisterDone( PGX ); + CheckGDXError( PGX ); +} + +void registerDiffUELs() +{ + if( diffUELsRegistered ) + return; + + const int maxUEL { ignoreOrder ? staticUELNum : UELTable->Count() }; + + gdxUELRegisterStrStart( PGXDIF ); + int d; + for( int N {}; N < maxUEL; N++ ) + gdxUELRegisterStr( PGXDIF, UELTable->GetString( N ), &d ); + gdxUELRegisterDone( PGXDIF ); + CheckGDXError( PGXDIF ); + + diffUELsRegistered = true; +} + +void CompareSy( const int Sy1, const int Sy2 ) +{ + int Dim, VarEquType; + gdxSyType ST; + library::short_string ID; + bool SymbOpen {}; + TStatusCode Status; + gdxValues_t DefValues {}; + + auto CheckSymbOpen = [&]() -> bool { + registerDiffUELs(); + if( Status == TStatusCode::sc_dim10 ) + Status = TStatusCode::sc_dim10_diff; + if( !SymbOpen && Status != TStatusCode::sc_dim10_diff ) + { + if( FldOnlyVar == FldOnly::fld_yes && ( ST == dt_var || ST == dt_equ ) ) + { + library::short_string ExplTxt { "Differences Field = " + GamsFieldNames[FldOnlyFld] }; + gdxDataWriteStrStart( PGXDIF, ID.data(), ExplTxt.data(), Dim + 1, static_cast( dt_par ), 0 ); + } + else if( DiffOnly && ( ST == dt_var || ST == dt_equ ) ) + gdxDataWriteStrStart( PGXDIF, ID.data(), "Differences Only", Dim + 2, static_cast( dt_par ), 0 ); + else + gdxDataWriteStrStart( PGXDIF, ID.data(), "Differences", Dim + 1, static_cast( ST ), VarEquType ); + SymbOpen = true; + } + return SymbOpen; + }; + + auto SymbClose = [&]() -> void { + if( SymbOpen ) + { + SymbOpen = false; + CheckGDXError( PGXDIF ); + gdxDataWriteDone( PGXDIF ); + CheckGDXError( PGXDIF ); + } + }; + + auto WriteDiff = [&]( const std::string &Act, const std::string &FldName, const gdxUelIndex_t &Keys, const gdxValues_t &Vals ) -> void { + gdxStrIndex_t StrKeys {}; + gdxStrIndexPtrs_t StrKeysPtrs; + GDXSTRINDEXPTRS_INIT( StrKeys, StrKeysPtrs ); + gdxValues_t Vals2 {}; + + registerDiffUELs(); + for( int D {}; D < Dim; D++ ) + // TODO: Improve this check (especially the else case) + if( Keys[D] < UELTable->Count() ) + strcpy( StrKeysPtrs[D], UELTable->GetString( Keys[D] ) ); + else + snprintf( StrKeysPtrs[D], GMS_SSSIZE, "L__%d", Keys[D] ); + if( !( DiffOnly && ( ST == dt_var || ST == dt_equ ) ) ) + strcpy( StrKeysPtrs[Dim], Act.data() ); + else + { + strcpy( StrKeysPtrs[Dim], FldName.data() ); + strcpy( StrKeysPtrs[Dim + 1], Act.data() ); + } + +#if VERBOSE >= 3 + for( int D {}; D < Dim + 1; D++ ) + { + std::cout << StrKeys[D]; + if( D < Dim + 1 ) + std::cout << ", "; + } + std::cout << std::endl; +#endif + + if( FldOnlyVar == FldOnly::fld_yes && ( ST == dt_var || ST == dt_equ ) ) + { + Vals2[GMS_VAL_LEVEL] = Vals[FldOnlyFld]; + gdxDataWriteStr( PGXDIF, const_cast( StrKeysPtrs ), Vals2 ); + } + else + gdxDataWriteStr( PGXDIF, const_cast( StrKeysPtrs ), Vals ); + }; + + auto WriteSetDiff = [&]( const std::string &Act, const gdxUelIndex_t &Keys, const library::short_string &S ) -> void { + gdxStrIndex_t StrKeys {}; + gdxStrIndexPtrs_t StrKeysPtrs; + GDXSTRINDEXPTRS_INIT( StrKeys, StrKeysPtrs ); + gdxValues_t Vals {}; + int iNode; + + registerDiffUELs(); + for( int D {}; D < Dim; D++ ) + strcpy( StrKeysPtrs[D], UELTable->GetString( Keys[D] ) ); + strcpy( StrKeysPtrs[Dim], Act.data() ); + gdxAddSetText( PGXDIF, S.data(), &iNode ); + Vals[GMS_VAL_LEVEL] = iNode; + gdxDataWriteStr( PGXDIF, const_cast( StrKeysPtrs ), Vals ); + }; + +#if VERBOSE >= 2 + auto WriteValues = [&]( const gdxHandle_t &PGX, const gdxValues_t &Vals ) -> void { + switch( ST ) + { + case dt_set: + library::assertWithMessage( false, "Should not be called" ); + break; + + case dt_par: + std::cout << ValAsString( PGX, Vals[GMS_VAL_LEVEL] ) << std::endl; + break; + + default: + for( int T {}; T < tvarvaltype_size; T++ ) + std::cout << ValAsString( PGX, Vals[T] ) << ' '; + std::cout << std::endl; + break; + } + }; + + auto WriteKeys = [&]( const gdxUelIndex_t &Keys ) -> void { + registerDiffUELs(); + for( int D {}; D < Dim; D++ ) + { + std::cout << ' ' << UELTable->GetString( Keys[D] ); + if( D < Dim ) + std::cout << " ."; + } + }; +#endif + + auto DoublesEqual = []( const double V1, const double V2 ) -> bool { + auto DMin = []( const double a, const double b ) -> double { + return a <= b ? a : b; + }; + + // auto DMax = []( const double a, const double b ) -> double { + // if( a >= b ) + // return a; + // else + // return b; + // }; + + int iSV1, iSV2; + gdxMapValue( PGX1, V1, &iSV1 ); + gdxMapValue( PGX2, V2, &iSV2 ); + + bool result; + library::short_string S1, S2; + double AbsDiff; + if( iSV1 == sv_normal ) + { + if( iSV2 == sv_normal ) + { + if( gdxAcronymName( PGX1, V1, S1.data() ) != 0 && gdxAcronymName( PGX2, V2, S2.data() ) != 0 ) + result = gdlib::strutilx::StrUEqual( S1.string(), S2.string() ); + else + { + AbsDiff = std::abs( V1 - V2 ); + if( AbsDiff <= EpsAbsolute ) + result = true; + else if( EpsRelative > 0 ) + result = AbsDiff / ( 1 + DMin( std::abs( V1 ), std::abs( V2 ) ) ) <= EpsRelative; + else + result = false; + } + } + else + result = iSV2 == sv_valeps && EpsAbsolute > 0 && std::abs( V1 ) <= EpsAbsolute; + } + else + { + if( iSV2 == sv_normal ) + result = iSV1 == sv_valeps && EpsAbsolute > 0 && std::abs( V2 ) <= EpsAbsolute; + else + result = iSV1 == iSV2; + } + return result; + }; + + auto CheckParDifference = [&]( const gdxUelIndex_t &Keys, const gdxValues_t &V1, const gdxValues_t &V2 ) -> bool { + bool result { true }; + if( ST == dt_par ) + result = DoublesEqual( V1[GMS_VAL_LEVEL], V2[GMS_VAL_LEVEL] ); + else if( FldOnlyVar == FldOnly::fld_yes ) + result = DoublesEqual( V1[FldOnlyFld], V2[FldOnlyFld] ); + else + { + for( int T {}; T < tvarvaltype_size; T++ ) + { + if( ActiveFields.find( static_cast( T ) ) != ActiveFields.end() && !DoublesEqual( V1[T], V2[T] ) ) + { + result = false; + break; + } + } + } + if( !result ) + { +#if VERBOSE >= 2 + std::cout << "Different "; + WriteKeys( Keys ); + std::cout << std::endl; + WriteValues( PGX1, V1 ); + WriteValues( PGX2, V2 ); +#endif + + if( !CheckSymbOpen() ) + return {}; + if( !( DiffOnly && ( ST == dt_var || ST == dt_equ ) ) ) + { + WriteDiff( c_dif1, {}, Keys, V1 ); + WriteDiff( c_dif2, {}, Keys, V2 ); + } + else + { + for( int T {}; T < tvarvaltype_size; T++ ) + { + if( ActiveFields.find( static_cast( T ) ) == ActiveFields.end() ) + continue; + if( DoublesEqual( V1[T], V2[T] ) ) + continue; + + gdxValues_t Vals {}; + Vals[GMS_VAL_LEVEL] = V1[T]; + WriteDiff( c_dif1, GamsFieldNames[T], Keys, Vals ); + Vals[GMS_VAL_LEVEL] = V2[T]; + WriteDiff( c_dif2, GamsFieldNames[T], Keys, Vals ); + } + } + } + return result; + }; + + auto CheckSetDifference = [&]( const gdxUelIndex_t &Keys, const int txt1, const int txt2 ) -> bool { + library::short_string S1, S2; + int iNode; + if( txt1 == 0 ) + S1.clear(); + else + gdxGetElemText( PGX1, txt1, S1.data(), &iNode ); + if( txt2 == 0 ) + S2.clear(); + else + gdxGetElemText( PGX2, txt2, S2.data(), &iNode ); + + bool result { S1 == S2 }; + if( !result ) + { +#if VERBOSE >= 2 + std::cout << "Associated text is different "; + WriteKeys( Keys ); + std::cout << std::endl; + std::cout << S1 << std::endl; + std::cout << S2 << std::endl; +#endif + + if( !CheckSymbOpen() ) + return {}; + WriteSetDiff( c_dif1, Keys, S1 ); + WriteSetDiff( c_dif2, Keys, S2 ); + } + return result; + }; + + auto ShowInsert = [&]( const std::string &Act, const gdxUelIndex_t &Keys, gdxValues_t &Vals ) -> void { + // We check if this insert has values we want to ignore + bool Eq {}; + switch( ST ) + { + case dt_par: + Eq = DoublesEqual( Vals[GMS_VAL_LEVEL], 0 ); + break; + + case dt_var: + case dt_equ: + Eq = true; + for( int T {}; T < tvarvaltype_size; T++ ) + { + if( ActiveFields.find( static_cast( T ) ) != ActiveFields.end() && !DoublesEqual( Vals[T], DefValues[T] ) ) + { + Eq = false; + break; + } + } + break; + + default: + // TODO: Print error message? + break; + } + + if( Eq && !ShowDefRec ) + return; + + if( Status == TStatusCode::sc_same ) + Status = TStatusCode::sc_key; + if( Status == TStatusCode::sc_dim10 ) + Status = TStatusCode::sc_dim10_diff; + + if( Status == TStatusCode::sc_dim10_diff || !CheckSymbOpen() ) + return; + +#if VERBOSE >= 2 + std::cout << "Insert: " << Act << ' '; +#endif + + if( ST == dt_set && Vals[GMS_VAL_LEVEL] != 0 ) + { + library::short_string stxt; + int N; + gdxGetElemText( Act == c_ins1 ? PGX1 : PGX2, static_cast( round( Vals[GMS_VAL_LEVEL] ) ), stxt.data(), &N ); + gdxAddSetText( PGXDIF, stxt.data(), &N ); + Vals[GMS_VAL_LEVEL] = N; + } + + if( !( DiffOnly && ( ST == dt_var || ST == dt_equ ) ) ) + WriteDiff( Act, {}, Keys, Vals ); + else + { + gdxValues_t Vals2 {}; + for( int T {}; T < tvarvaltype_size; T++ ) + { + if( ActiveFields.find( static_cast( T ) ) == ActiveFields.end() ) + continue; + Vals2[GMS_VAL_LEVEL] = Vals[T]; + WriteDiff( Act, GamsFieldNames[T], Keys, Vals2 ); + } + } + }; + + int Dim2, AFDim, iST, iST2, R1Last, R2Last, C, acount; + gdxSyType ST2; + bool Flg1, Flg2, Eq, DomFlg; + gdxUelIndex_t Keys1 {}, Keys2 {}; + gdxValues_t Vals1 {}, Vals2 {}; + library::short_string stxt; + gdxStrIndex_t DomSy1 {}; + gdxStrIndexPtrs_t DomSy1Ptrs; + GDXSTRINDEXPTRS_INIT( DomSy1, DomSy1Ptrs ); + gdxStrIndex_t DomSy2 {}; + gdxStrIndexPtrs_t DomSy2Ptrs; + GDXSTRINDEXPTRS_INIT( DomSy2, DomSy2Ptrs ); + + SymbOpen = false; + gdxSymbolInfo( PGX1, Sy1, ID.data(), &Dim, &iST ); + ST = static_cast( iST ); + if( ST == dt_alias ) + ST = dt_set; + // We do nothing with type in file2 + gdxSymbolInfoX( PGX1, Sy1, &acount, &VarEquType, stxt.data() ); + + gdxSymbolInfo( PGX2, Sy2, ID.data(), &Dim2, &iST2 ); + ST2 = static_cast( iST2 ); + if( ST2 == dt_alias ) + ST2 = dt_set; + Status = TStatusCode::sc_same; + + if( Dim != Dim2 || ST != ST2 ) + { + std::cout << "*** symbol = " << ID << " cannot be compared\n"; + if( ST != ST2 ) + { + std::cout << "Typ1 = " << library::gdxDataTypStrL( ST ) << ", Typ2 = " << library::gdxDataTypStrL( ST2 ) << '\n'; + Status = TStatusCode::sc_typ; + } + if( Dim != Dim2 ) + { + std::cout << "Dim1 = " << Dim << ", Dim2 = " << Dim2 << std::endl; + if( Status == TStatusCode::sc_same ) + Status = TStatusCode::sc_dim; + } + goto label999; + } + + // Check domains + if( CompDomains && Dim > 0 ) + { + gdxSymbolGetDomainX( PGX1, Sy1, DomSy1Ptrs ); + gdxSymbolGetDomainX( PGX2, Sy2, DomSy2Ptrs ); + DomFlg = false; + for( int D {}; D < Dim; D++ ) + if( !gdlib::strutilx::StrUEqual( DomSy1[D], DomSy2[D] ) ) + { + DomFlg = true; + break; + } + if( DomFlg ) + { + Status = TStatusCode::sc_domain; + +#if VERBOSE >= 1 + std::cout << "Domain differences for symbol = " << ID << '\n'; + for( int D {}; D < Dim; D++ ) + { + if( gdlib::strutilx::StrUEqual( DomSy1[D], DomSy2[D] ) ) + continue; + std::cout << gdlib::strutilx::PadRight( std::to_string( D ), 2 ) << ' ' << DomSy1[D] << ' ' << DomSy2[D] << '\n'; + } + std::cout << std::endl; +#endif + + goto label999; + } + } + +#if VERBOSE >= 1 + std::cout << library::gdxDataTypStrL( ST ) << ' ' << ID << std::endl; +#endif + + // Create default record for this type + if( ST == dt_var || ST == dt_equ ) + { + if( ST == dt_var ) + std::copy( std::begin( gmsDefRecVar[VarEquType] ), std::end( gmsDefRecVar[VarEquType] ), std::begin( DefValues ) ); + else + std::copy( std::begin( gmsDefRecEqu[VarEquType] ), std::end( gmsDefRecEqu[VarEquType] ), std::begin( DefValues ) ); + } + + if( Dim >= GMS_MAX_INDEX_DIM || ( DiffOnly && Dim - 1 >= GMS_MAX_INDEX_DIM ) ) + Status = TStatusCode::sc_dim10; + + if( matrixFile ) + { + Flg1 = gdxDataReadRawStart( PGX1, Sy1, &R1Last ) != 0; + if( Flg1 ) + Flg1 = gdxDataReadRaw( PGX1, Keys1, Vals1, &AFDim ) != 0; + + Flg2 = gdxDataReadRawStart( PGX2, Sy2, &R2Last ) != 0; + if( Flg2 ) + Flg2 = gdxDataReadRaw( PGX2, Keys2, Vals2, &AFDim ) != 0; + } + else + { + Flg1 = gdxDataReadMapStart( PGX1, Sy1, &R1Last ) != 0; + if( Flg1 ) + Flg1 = gdxDataReadMap( PGX1, 0, Keys1, Vals1, &AFDim ) != 0; + + Flg2 = gdxDataReadMapStart( PGX2, Sy2, &R2Last ) != 0; + if( Flg2 ) + Flg2 = gdxDataReadMap( PGX2, 0, Keys2, Vals2, &AFDim ) != 0; + } + + while( Flg1 && Flg2 ) + { + C = 0; + if( Dim > 0 ) + { + for( int D {}; D < Dim; D++ ) + { + C = Keys1[D] - Keys2[D]; + if( C != 0 ) + break; + } + } + if( C == 0 ) + { + if( ST == dt_set ) + { + if( CompSetText ) + Eq = CheckSetDifference( Keys1, static_cast( round( Vals1[GMS_VAL_LEVEL] ) ), static_cast( round( Vals2[GMS_VAL_LEVEL] ) ) ); + else + Eq = true; + } + else + Eq = CheckParDifference( Keys1, Vals1, Vals2 ); + + if( !Eq && Status == TStatusCode::sc_same ) + Status = TStatusCode::sc_data; + + if( matrixFile ) + { + Flg1 = gdxDataReadRaw( PGX1, Keys1, Vals1, &AFDim ) != 0; + Flg2 = gdxDataReadRaw( PGX2, Keys2, Vals2, &AFDim ) != 0; + } + else + { + Flg1 = gdxDataReadMap( PGX1, 0, Keys1, Vals1, &AFDim ) != 0; + Flg2 = gdxDataReadMap( PGX2, 0, Keys2, Vals2, &AFDim ) != 0; + } + } + // Change in status happens inside ShowInsert + else if( C < 0 ) + { + ShowInsert( c_ins1, Keys1, Vals1 ); + if( matrixFile ) + Flg1 = gdxDataReadRaw( PGX1, Keys1, Vals1, &AFDim ) != 0; + else + Flg1 = gdxDataReadMap( PGX1, 0, Keys1, Vals1, &AFDim ) != 0; + } + else + { + ShowInsert( c_ins2, Keys2, Vals2 ); + if( matrixFile ) + Flg2 = gdxDataReadRaw( PGX2, Keys2, Vals2, &AFDim ) != 0; + else + Flg2 = gdxDataReadMap( PGX2, 0, Keys2, Vals2, &AFDim ) != 0; + } + } + + while( Flg1 ) + { + ShowInsert( c_ins1, Keys1, Vals1 ); + if( matrixFile ) + Flg1 = gdxDataReadRaw( PGX1, Keys1, Vals1, &AFDim ) != 0; + else + Flg1 = gdxDataReadMap( PGX1, 0, Keys1, Vals1, &AFDim ) != 0; + } + + while( Flg2 ) + { + ShowInsert( c_ins2, Keys2, Vals2 ); + if( matrixFile ) + Flg2 = gdxDataReadRaw( PGX2, Keys2, Vals2, &AFDim ) != 0; + else + Flg2 = gdxDataReadMap( PGX2, 0, Keys2, Vals2, &AFDim ) != 0; + } + + SymbClose(); + +label999: + if( !( Status == TStatusCode::sc_same || Status == TStatusCode::sc_dim10 ) ) + StatusTable.insert( { ID, Status } ); +} + +bool GetAsDouble( const library::short_string &S, double &V ) +{ + int k; + utils::val( S.string(), V, k ); + bool result { k == 0 && V >= 0 }; + if( !result ) + V = 0; + return result; +} + +void Usage( const library::AuditLine &AuditLine ) +{ + std::cout << "gdxdiff: GDX file differ\n" + << AuditLine.getAuditLine() << "\n\n" + << "Usage: \n" + << " gdxdiff file1.gdx file2.gdx [diffile.gdx] [options]\n" + << " Options:\n" + << " Eps = Val epsilon for comparison\n" + << " RelEps = Val epsilon for relative comparison\n" + << " Field = gamsfield (L, M, Up, Lo, Prior, Scale or All)\n" + << " FldOnly write var or equ as parameter for selected field\n" + << " DiffOnly write var or equ as parameter with field as an extra dimension\n" + << " CmpDefaults compare default values\n" + << " CmpDomains compare domains\n" + << " MatrixFile compare GAMS matrix files in GDX format\n" + << " IgnoreOrder ignore UEL order of input files - reduces size of output file\n" + << " SetDesc = Y|N compare explanatory texts for set elements, activated by default (=Y)\n" + << " ID = one or more identifiers; only ids listed will be compared\n" + << " SkipID = one or more identifiers; ids listed will be skipped\n" + << " The .gdx file extension is the default" << std::endl; +} + +// Function is empty in Delphi code +// void CopyAcronyms( const gdxHandle_t &PGX ) {} + +void CheckFile( library::short_string &fn ) +{ + if( !rtl::sysutils_p3::FileExists( fn.string() ) && gdlib::strutilx::ExtractFileExtEx( fn.string() ).empty() ) + fn = gdlib::strutilx::ChangeFileExtEx( fn.string(), ".gdx" ); +} + +int main( const int argc, const char *argv[] ) +{ + int ErrorCode, ErrNr, Dim, iST, StrNr; + library::short_string S, ID, InFile1, InFile2, DiffFileName; + std::map IDTable; + bool UsingIDE, RenameOK; + gdxStrIndex_t StrKeys {}; + gdxStrIndexPtrs_t StrKeysPtrs; + GDXSTRINDEXPTRS_INIT( StrKeys, StrKeysPtrs ); + gdxValues_t StrVals {}; + + library::AuditLine AuditLine { "GDXDIFF" }; + if( argc > 1 && gdlib::strutilx::StrUEqual( argv[1], "AUDIT" ) ) + { + std::cout << AuditLine.getAuditLine() << std::endl; + return 0; + } + + // So we can check later + DiffTmpName.clear(); + + CmdParams = std::make_unique(); + + CmdParams->AddParam( static_cast( library::cmdpar::CmdParamStatus::kp_input ), "I" ); + CmdParams->AddParam( static_cast( library::cmdpar::CmdParamStatus::kp_input ), "Input" ); + CmdParams->AddParam( static_cast( KP::kp_output ), "Output" ); + CmdParams->AddParam( static_cast( KP::kp_output ), "O" ); + CmdParams->AddParam( static_cast( KP::kp_eps ), "Eps" ); + CmdParams->AddParam( static_cast( KP::kp_releps ), "RelEps" ); + CmdParams->AddParam( static_cast( KP::kp_cmpfld ), "Field" ); + CmdParams->AddParam( static_cast( KP::kp_settext ), "SetDesc" ); + CmdParams->AddParam( static_cast( KP::kp_ide ), "IDE" ); + CmdParams->AddParam( static_cast( KP::kp_id ), "ID" ); + CmdParams->AddParam( static_cast( KP::kp_skip_id ), "SkipID" ); + + CmdParams->AddKeyWord( static_cast( KP::kp_fldonly ), "FldOnly" ); + CmdParams->AddKeyWord( static_cast( KP::kp_diffonly ), "DiffOnly" ); + CmdParams->AddKeyWord( static_cast( KP::kp_showdef ), "CmpDefaults" ); + CmdParams->AddKeyWord( static_cast( KP::kp_cmpdomain ), "CmpDomains" ); + CmdParams->AddKeyWord( static_cast( KP::kp_matrixfile ), "MatrixFile" ); + CmdParams->AddKeyWord( static_cast( KP::kp_ignoreOrd ), "IgnoreOrder" ); + + if( !CmdParams->CrackCommandLine( argc, argv ) ) + { + Usage( AuditLine ); + return static_cast( ErrorCode::ERR_USAGE ); + } + + ErrorCode = 0; + UsingIDE = false; + matrixFile = false; + diffUELsRegistered = false; + + library::cmdpar::TParamRec Parameter { CmdParams->GetParams( 0 ) }; + if( Parameter.Key == static_cast( library::cmdpar::CmdParamStatus::kp_input ) ) + InFile1 = Parameter.KeyS; + // else + // InFile1.clear(); + + Parameter = CmdParams->GetParams( 1 ); + if( Parameter.Key == static_cast( library::cmdpar::CmdParamStatus::ke_unknown ) ) + InFile2 = Parameter.KeyS; + // else + // InFile2.clear(); + + if( InFile1.empty() || InFile2.empty() ) + ErrorCode = 1; + + if( !CmdParams->HasParam( static_cast( KP::kp_output ), DiffFileName ) ) + { + Parameter = CmdParams->GetParams( 2 ); + if( Parameter.Key == static_cast( library::cmdpar::CmdParamStatus::ke_unknown ) ) + DiffFileName = Parameter.KeyS; + // else + // DiffFileName.clear(); + } + + if( DiffFileName.empty() ) + DiffFileName = "diffile"; + + if( gdlib::strutilx::ExtractFileExtEx( DiffFileName.string() ).empty() ) + DiffFileName = gdlib::strutilx::ChangeFileExtEx( DiffFileName.string(), ".gdx" ); + + if( !CmdParams->HasParam( static_cast( KP::kp_eps ), S ) ) + EpsAbsolute = 0; + else if( GetAsDouble( S, EpsAbsolute ) ) + { + if( EpsAbsolute < 0 ) + { + std::cout << "Eps cannot be negative" << std::endl; + ErrorCode = 2; + } + } + else + { + std::cout << "Bad value for Eps = " << S << std::endl; + ErrorCode = 2; + } + + if( !CmdParams->HasParam( static_cast( KP::kp_releps ), S ) ) + EpsRelative = 0; + else if( GetAsDouble( S, EpsRelative ) ) + { + if( EpsRelative < 0 ) + { + std::cout << "RelEps cannot be negative" << std::endl; + ErrorCode = 2; + } + } + else + { + std::cout << "Bad value for RelEps = " << S << std::endl; + ErrorCode = 2; + } + + DiffOnly = CmdParams->HasKey( static_cast( KP::kp_diffonly ) ); + FldOnlyVar = FldOnly::fld_no; + matrixFile = CmdParams->HasKey( static_cast( KP::kp_matrixfile ) ); + ignoreOrder = CmdParams->HasKey( static_cast( KP::kp_ignoreOrd ) ); + + if( !CmdParams->HasParam( static_cast( KP::kp_cmpfld ), S ) ) + ActiveFields = { GMS_VAL_LEVEL, GMS_VAL_MARGINAL, GMS_VAL_LOWER, GMS_VAL_UPPER, GMS_VAL_SCALE }; + else + { + if( gdlib::strutilx::StrUEqual( S.string(), "All" ) ) + ActiveFields = { GMS_VAL_LEVEL, GMS_VAL_MARGINAL, GMS_VAL_LOWER, GMS_VAL_UPPER, GMS_VAL_SCALE }; + else if( gdlib::strutilx::StrUEqual( S.string(), "L" ) ) + { + FldOnlyFld = GMS_VAL_LEVEL; + FldOnlyVar = FldOnly::fld_maybe; + } + else if( gdlib::strutilx::StrUEqual( S.string(), "M" ) ) + { + FldOnlyFld = GMS_VAL_MARGINAL; + FldOnlyVar = FldOnly::fld_maybe; + } + else if( gdlib::strutilx::StrUEqual( S.string(), "Up" ) ) + { + FldOnlyFld = GMS_VAL_UPPER; + FldOnlyVar = FldOnly::fld_maybe; + } + else if( gdlib::strutilx::StrUEqual( S.string(), "Lo" ) ) + { + FldOnlyFld = GMS_VAL_LOWER; + FldOnlyVar = FldOnly::fld_maybe; + } + else if( gdlib::strutilx::StrUEqual( S.string(), "Prior" ) || gdlib::strutilx::StrUEqual( S.string(), "Scale" ) ) + { + FldOnlyFld = GMS_VAL_SCALE; + FldOnlyVar = FldOnly::fld_maybe; + } + else + { + std::cout << "Bad field name = " << S << std::endl; + ErrorCode = 4; + } + + if( FldOnlyVar == FldOnly::fld_maybe ) + ActiveFields = { FldOnlyFld }; + } + + if( CmdParams->HasKey( static_cast( KP::kp_fldonly ) ) ) + { + if( FldOnlyVar == FldOnly::fld_maybe ) + { + FldOnlyVar = FldOnly::fld_yes; + if( DiffOnly ) + { + // TODO: Change combines to combined? + std::cout << "Diff only cannot be combined with FldOnly" << std::endl; + ErrorCode = 4; + } + } + else + { + std::cout << "FldOnly option used with a single field comparison" << std::endl; + ErrorCode = 4; + } + } + + if( CmdParams->HasParam( static_cast( KP::kp_ide ), S ) ) + UsingIDE = !S.empty() && ( S.front() == 'Y' || S.front() == 'y' || S.front() == '1' ); + + CompSetText = true; + + // This is a mistake; should be HasKey but leave it + if( CmdParams->HasParam( static_cast( KP::kp_settext ), S ) ) + { + S = gdlib::strutilx::UpperCase( S.string() ); + if( S == "0" || S == "N" || S == "F" ) + CompSetText = false; + else if( S.empty() || S == "1" || S == "Y" || S == "T" ) + CompSetText = true; + else + { + std::cout << "Bad value for CompSetText = " << S << std::endl; + ErrorCode = 4; + } + } + + ShowDefRec = CmdParams->HasKey( static_cast( KP::kp_showdef ) ); + CompDomains = CmdParams->HasKey( static_cast( KP::kp_cmpdomain ) ); + + if( CmdParams->HasParam( static_cast( KP::kp_id ), S ) ) + { + IDsOnly = std::make_unique(); + for( int N {}; N < CmdParams->GetParamCount(); N++ ) + { + if( CmdParams->GetParams( N ).Key == static_cast( KP::kp_id ) ) + { + S = utils::trim( CmdParams->GetParams( N ).KeyS ); + // std::cout << S << std::endl; + while( !S.empty() ) + { + int k { gdlib::strutilx::LChSetPos( + ", ", + S.data(), + static_cast( S.length() ) ) }; + if( k == -1 ) + { + ID = S; + S.clear(); + } + else + { + ID = S.string().substr( 0, k ); + S = S.string().erase( 0, k + 1 ); + S = utils::trim( S.string() ); + } + ID = utils::trim( ID.string() ); + if( !ID.empty() ) + { + if( IDsOnly->IndexOf( ID.data() ) < 0 ) + IDsOnly->Add( ID.data(), ID.length() ); + // std::cout << "Include ID: " << ID << std::endl; + } + } + } + } + } + + // if( IDsOnly && IDsOnly->GetCount() == 0 ) + // // Like ID = "" (or ID.empty()) + // FreeAndNil( IDsOnly ); + + if( CmdParams->HasParam( static_cast( KP::kp_skip_id ), S ) ) + { + SkipIDs = std::make_unique(); + for( int N {}; N < CmdParams->GetParamCount(); N++ ) + { + if( CmdParams->GetParams( N ).Key == static_cast( KP::kp_skip_id ) ) + { + S = utils::trim( CmdParams->GetParams( N ).KeyS ); + // std::cout << S << std::endl; + while( !S.empty() ) + { + int k { gdlib::strutilx::LChSetPos( + ", ", + S.data(), + static_cast( S.length() ) ) }; + if( k == -1 ) + { + ID = S; + S.clear(); + } + else + { + ID = S.string().substr( 0, k ); + S = S.string().erase( 0, k + 1 ); + S = utils::trim( S.string() ); + } + ID = utils::trim( ID.string() ); + if( !ID.empty() ) + { + if( SkipIDs->IndexOf( ID.data() ) < 0 ) + SkipIDs->Add( ID.data(), ID.length() ); + // std::cout << "Skip ID: " << ID << std::endl; + } + } + } + } + } + + // We removed this but not sure why + // if( rtl::sysutils_p3::FileExists( DiffFileName ) ) + // rtl::sysutils_p3::DeleteFileFromDisk( DiffFileName ); + + // Parameter errors + if( ErrorCode > 0 ) + { + // TODO: Remove? + // std::cout << std::endl; + Usage( AuditLine ); + return static_cast( ErrorCode::ERR_USAGE ); + } + + std::cout << AuditLine.getAuditLine() << std::endl; + + CheckFile( InFile1 ); + CheckFile( InFile2 ); + + std::cout << "File1 : " << InFile1 << std::endl; + std::cout << "File2 : " << InFile2 << std::endl; + + if( IDsOnly ) + { + std::cout << "ID :"; + for( int N {}; N < IDsOnly->GetCount(); N++ ) + std::cout << ' ' << IDsOnly->GetConst( N ); + std::cout << std::endl; + } + + if( SkipIDs ) + { + std::cout << "SkipID:"; + for( int N {}; N < SkipIDs->GetCount(); N++ ) + std::cout << ' ' << SkipIDs->GetConst( N ); + std::cout << std::endl; + } + + library::short_string S2; + if( !gdxCreate( &PGXDIF, S2.data(), S2.length() ) ) + FatalError( "Unable to load GDX library: " + S2.string(), static_cast( ErrorCode::ERR_LOADDLL ) ); + + // Temporary file name + for( int N { 1 }; N <= std::numeric_limits::max(); N++ ) + { + DiffTmpName = "tmpdifffile" + std::to_string( N ) + ".gdx"; + if( !rtl::sysutils_p3::FileExists( DiffTmpName.string() ) ) + break; + } + + gdxOpenWrite( PGXDIF, DiffTmpName.data(), "GDXDIFF", &ErrNr ); + if( ErrNr != 0 ) + { + int N { gdxGetLastError( PGXDIF ) }; + // Nil is used instead of PGXDIF in Delphi code + gdxErrorStr( PGXDIF, N, S.data() ); + FatalError2( "Cannot create file: " + DiffTmpName.string(), S.string(), static_cast( ErrorCode::ERR_WRITEGDX ) ); + } + + UELTable = std::make_unique>(); + // UELTable->OneBased = true; + gdxStoreDomainSetsSet( PGXDIF, false ); + + UELTable->Add( c_ins1.data(), c_ins1.length() ); + UELTable->Add( c_ins2.data(), c_ins2.length() ); + UELTable->Add( c_dif1.data(), c_dif1.length() ); + UELTable->Add( c_dif2.data(), c_dif2.length() ); + + if( DiffOnly ) + for( int T {}; T < tvarvaltype_size; T++ ) + UELTable->Add( GamsFieldNames[T].data(), GamsFieldNames[T].length() ); + + staticUELNum = UELTable->Count(); + + // These calls register UELs + OpenGDX( InFile1, PGX1 ); + OpenGDX( InFile2, PGX2 ); + + { + int N { 1 }; + while( gdxSymbolInfo( PGX1, N, ID.data(), &Dim, &iST ) != 0 ) + { + if( ( !IDsOnly || IDsOnly->IndexOf( ID.data() ) >= 0 ) && + ( !SkipIDs || SkipIDs->IndexOf( ID.data() ) < 0 ) ) + IDTable.insert( { ID, N } ); + N++; + } + } + + for( const auto &pair: IDTable ) + { + int NN; + if( gdxFindSymbol( PGX2, pair.first.data(), &NN ) != 0 ) + CompareSy( pair.second, NN ); + else + StatusTable.insert( { pair.first, TStatusCode::sc_notf2 } ); + } + + // Find symbols in file 2 that are not in file 1 + IDTable.clear(); + + for( int N { 1 }; gdxSymbolInfo( PGX2, N, ID.data(), &Dim, &iST ) != 0; N++ ) + { + if( ( !IDsOnly || IDsOnly->IndexOf( ID.data() ) >= 0 ) && + ( !SkipIDs || SkipIDs->IndexOf( ID.data() ) < 0 ) ) + IDTable.insert( { ID, N } ); + } + + for( const auto &pair: IDTable ) + { + int NN; + if( gdxFindSymbol( PGX1, pair.first.data(), &NN ) == 0 ) + StatusTable.insert( { pair.first, TStatusCode::sc_notf1 } ); + } + + if( StatusTable.empty() ) + std::cout << "No differences found" << std::endl; + else + { + std::cout << "Summary of differences:" << std::endl; + // Find longest ID + int NN { 1 }; + for( const auto &pair: StatusTable ) + if( pair.first.length() > NN ) + NN = static_cast( pair.first.length() ); + for( const auto &pair: StatusTable ) + std::cout << gdlib::strutilx::PadLeft( pair.first.string(), NN ) << " " + << StatusText.at( static_cast( pair.second ) ) << std::endl; + } + + // CopyAcronyms( PGX1 ); + // CopyAcronyms( PGX2 ); + + // Write the two filenames used as explanatory texts + { + + int N {}; + int NN; + do { + ID = "FilesCompared"; + if( N > 0 ) + ID += std::to_string( '_' ) + std::to_string( N ); + if( gdxFindSymbol( PGXDIF, ID.data(), &NN ) == 0 ) + break; + N++; + } while( true ); + } + + gdxDataWriteStrStart( PGXDIF, ID.data(), {}, 1, static_cast( dt_set ), 0 ); + strcpy( StrKeysPtrs[0], "File1" ); + gdxAddSetText( PGXDIF, InFile1.data(), &StrNr ); + StrVals[GMS_VAL_LEVEL] = StrNr; + gdxDataWriteStr( PGXDIF, const_cast( StrKeysPtrs ), StrVals ); + strcpy( StrKeysPtrs[0], "File2" ); + gdxAddSetText( PGXDIF, InFile2.data(), &StrNr ); + StrVals[GMS_VAL_LEVEL] = StrNr; + gdxDataWriteStr( PGXDIF, const_cast( StrKeysPtrs ), StrVals ); + gdxDataWriteDone( PGXDIF ); + + // Note that input files are not closed at this point; so if we wrote + // to an input file, the delete will fail and we keep the original input file alive + gdxClose( PGXDIF ); + gdxFree( &PGXDIF ); + + if( !rtl::sysutils_p3::FileExists( DiffFileName.string() ) ) + RenameOK = true; + else + { + RenameOK = rtl::sysutils_p3::DeleteFileFromDisk( DiffFileName.string() ); +#if defined( _WIN32 ) + if( !RenameOK ) + { + int ShellCode; + if( rtl::p3process::P3ExecP( "IDECmds.exe ViewClose \"" + DiffFileName.string() + "\"", ShellCode ) == 0 ) + RenameOK = rtl::sysutils_p3::DeleteFileFromDisk( DiffFileName.string() ); + } +#endif + } + + if( RenameOK ) + { + rtl::sysutils_p3::RenameFile( DiffTmpName.string(), DiffFileName.string() ); + RenameOK = rtl::sysutils_p3::FileExists( DiffFileName.string() ); + } + + gdxClose( PGX1 ); + gdxFree( &PGX1 ); + gdxClose( PGX2 ); + gdxFree( &PGX2 ); + + int ExitCode {}; + if( !RenameOK ) + { + std::cout << "Could not rename " << DiffTmpName << " to " << DiffFileName << std::endl; + DiffFileName = DiffTmpName; + ExitCode = static_cast( ErrorCode::ERR_RENAME ); + } + std::cout << "Output: " << DiffFileName << std::endl; + + // UnloadGDXLibrary; + if( UsingIDE ) + std::cout << "GDXDiff file written: " << DiffFileName << "[FIL:\"" << DiffFileName << "\",0,0]" << std::endl; + std::cout << "GDXDiff finished" << std::endl; + // FreeAndNil( UELTable ); + + if( ExitCode == 0 && !StatusTable.empty() ) + return static_cast( ErrorCode::ERR_DIFFERENT ); + + return ExitCode; +} + +}// namespace gdxdiff + +int main( const int argc, const char *argv[] ) +{ + return gdxdiff::main( argc, argv ); +} diff --git a/src/tools/gdxdiff/gdxdiff.h b/src/tools/gdxdiff/gdxdiff.h new file mode 100644 index 0000000..d9ecfea --- /dev/null +++ b/src/tools/gdxdiff/gdxdiff.h @@ -0,0 +1,130 @@ +#ifndef GDX_GDXDIFF_H +#define GDX_GDXDIFF_H + +#include +#include +#include +#include + +#include "../library/common.h" +#include "../library/short_string.h" + +// GDX library interface +#include "../../../generated/gdxcc.h" + +namespace gdxdiff +{ + +using namespace std::literals::string_literals; + +enum class ErrorCode : uint8_t +{ + ERR_DIFFERENT = 1, + ERR_USAGE, + ERR_NOFILE, + ERR_READGDX, + ERR_LOADDLL, + ERR_WRITEGDX, + ERR_RENAME +}; + +enum class FldOnly : uint8_t +{ + fld_maybe, + fld_yes, + fld_no, + fld_never +}; + +enum class TStatusCode : uint8_t +{ + sc_same, + sc_notf1, + sc_notf2, + sc_dim, + sc_typ, + sc_key, + sc_data, + sc_dim10, + sc_dim10_diff, + sc_domain +}; + +// TODO: Name correctly +enum class KP : uint8_t +{ + // kp_input, + kp_eps = 1, + kp_releps, + kp_output, + kp_cmpfld, + kp_settext, + kp_ide, + kp_id, + kp_fldonly, + kp_diffonly, + kp_showdef, + kp_cmpdomain, + kp_matrixfile, + kp_ignoreOrd, + kp_skip_id +}; + +constexpr std::array StatusText { + "Symbols are identical", + "Symbol not found in file 1", + "Symbol not found in file 2", + "Dimensions are different", + "Types are different", + "Keys are different", + "Data are different", + "Dim >= maxdim", + "Dim >= maxdim & different", + "Domains are different" }; + +const std::string + c_ins1 = "ins1", + c_ins2 = "ins2", + c_dif1 = "dif1", + c_dif2 = "dif2"; + +// Size of gdx::tvarvaltype +constexpr int tvarvaltype_size { 5 }; + +// Names of gdx::tvarvaltype(s) +const std::array GamsFieldNames { + "Level"s, + "Marginal"s, + "Lower"s, + "Upper"s, + "Scale"s }; + +std::string ValAsString( const gdxHandle_t &PGX, double V ); + +void FatalErrorExit( int ErrNr ); + +void FatalError( const std::string &Msg, int ErrNr ); + +void FatalError2( const std::string &Msg1, const std::string &Msg2, int ErrNr ); + +void CheckGDXError( const gdxHandle_t &PGX ); + +void OpenGDX( const library::short_string &fn, gdxHandle_t &PGX ); + +void registerDiffUELs(); + +void CompareSy( int Sy1, int Sy2 ); + +bool GetAsDouble( const library::short_string &S, double &V ); + +void Usage( const library::AuditLine &AuditLine ); + +// void CopyAcronyms( const gdxHandle_t &PGX ); + +void CheckFile( library::short_string &fn ); + +int main( int argc, const char *argv[] ); + +}// namespace gdxdiff + +#endif//GDX_GDXDIFF_H diff --git a/src/tools/gdxdump/gdxdump.cpp b/src/tools/gdxdump/gdxdump.cpp new file mode 100644 index 0000000..b3567f1 --- /dev/null +++ b/src/tools/gdxdump/gdxdump.cpp @@ -0,0 +1,1635 @@ +/** + * GAMS - General Algebraic Modeling System C++ API + * + * Copyright (c) 2017-2024 GAMS Software GmbH + * Copyright (c) 2017-2024 GAMS Development Corp. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gdxdump.h" +#include "../library/short_string.h" +#include "../../gdlib/utils.h" +#include "../../gdlib/strutilx.h" +#include "../../gdlib/dblutil.h" + +// Global constants +#include "../../../generated/gclgms.h" +// GDX library interface +#include "../../../generated/gdxcc.h" + +namespace gdxdump +{ + +using namespace std::literals::string_literals; + +std::ostream &fo { std::cout }; +std::ofstream OutputFile; +gdxHandle_t PGX; +char Delim, DecimalSep; +bool ShowHdr, ShowData, CDim, FilterDef, bEpsOut, bNaOut, bPinfOut, bMinfOut, bUndfOut, bZeroOut, bHeader, bFullEVRec, bCSVSetText; +TOutFormat OutFormat; +TDblFormat dblFormat; +int LineCount; +std::string EpsOut, NaOut, PinfOut, MinfOut, UndfOut, ZeroOut, Header; + +char QQ( const std::string &s ) +{ + return s.find( '\'' ) == std::string::npos ? '\'' : '\"'; +} + +// No risk of overflow; only used for IDs, UELs or known strings +std::string QQCSV( const std::string &s ) +{ + if( Delim == '\t' ) + return s; + auto k = s.find( '\"' ); + if( k == std::string::npos ) + return "\"" + s + "\""; + std::string res = s.substr( 0, k + 1 ) + '\"',// two quotes + ws = s.substr( k + 1, GMS_SSSIZE - 1 ); + do { + k = ws.find( '\"' ); + if( k == std::string::npos ) + break; + res += ws.substr( 0, k + 1 ) + '\"';// two quotes + ws = ws.substr( k + 1 ); + } while( true ); + return '\"' + res + ws + '\"'; +} + +// Function is commented out in the Delphi source code +// std::string QUEL( const std::string &S ) +// { +// return {}; +// } + +void WriteQuotedCommon( const std::string &S, const std::function &isSpecialPredicate, const int i, bool G ) +{ + for( int k { i }; k < static_cast( S.length() ); k++ ) + { + const char c = S[k]; + if( isSpecialPredicate( c ) || ( c == '.' && k + 1 < static_cast( S.length() ) && S[k + 1] == '.' ) ) + { + G = false; + break; + } + } + if( G ) + fo << ' ' << S; + else + { + const auto QChar = QQ( S ); + fo << ' ' << QChar << S << QChar; + } +} + +// Write explanatory text with quotes if needed +void WriteQText( const std::string &S, const bool checkParenthesis ) +{ + size_t i {}; + bool G { true }; + + if( checkParenthesis ) + for( ; i < S.length(); i++ ) + if( S[i] != ' ' ) + { + // open parenthesis at the start of the symbol text throws off the GAMS compiler + G = S[i] != '('; + break; + } + + WriteQuotedCommon( S, []( const char c ) { + return utils::in( c, ',', ';', '/', '$', '=', '\'', '\"' ); + }, static_cast( i ), G ); +} + +// Check if UEL associated text needs to be quoted +void WriteQUELText( const std::string &S ) +{ + WriteQuotedCommon( S, []( const char c ) { + return utils::in( c, ' ', ',', ';', '/', '$', '=', '*' ); + } ); +} + +void WriteUEL( const std::string &S ) +{ + const auto QChar = QQ( S ); + fo << QChar << S << QChar; +} + +void WriteUELTable( const std::string &name ) +{ + int N, NrUel; + gdxSystemInfo( PGX, &N, &NrUel ); + if( OutFormat != TOutFormat::fmt_csv ) + { + if( NrUel > 0 ) + fo << "Set " << name << " /\n"; + else + { + fo << "$onEmpty\n" + << "Set " << name << "(*) / /;\n" + << "$offEmpty\n"; + } + } + for( N = 1; N <= NrUel; N++ ) + { + library::short_string s; + int UMap; + gdxUMUelGet( PGX, N, s.data(), &UMap ); + fo << " "; + WriteUEL( s.data() ); + if( OutFormat == TOutFormat::fmt_csv ) + fo << '\n'; + else + fo << ( N < NrUel ? " ," : " /;" ) << '\n'; + } +} + +void WrVal( const double V ) +{ + library::short_string acrname; + if( gdxAcronymName( PGX, V, acrname.data() ) != 0 ) + fo << acrname.data(); + else + { + int iSV; + gdxMapValue( PGX, V, &iSV ); + if( iSV != sv_normal ) + { + if( bEpsOut && iSV == sv_valeps ) + fo << EpsOut; + else if( bNaOut && iSV == sv_valna ) + fo << NaOut; + else if( bPinfOut && iSV == sv_valpin ) + fo << PinfOut; + else if( bMinfOut && iSV == sv_valmin ) + fo << MinfOut; + else if( bUndfOut && iSV == sv_valund ) + fo << UndfOut; + else + fo << library::specialValueStr( iSV ); + } + else if( bZeroOut && V == 0 ) + fo << ZeroOut; + else + switch( dblFormat ) + { + case TDblFormat::dbl_normal: + fo << gdlib::strutilx::DblToStrSep( V, DecimalSep ); + break; + case TDblFormat::dbl_hexBytes: + fo << gdlib::dblutil::dblToStrHex( V ); + break; + case TDblFormat::dbl_hexponential: + fo << gdlib::dblutil::dblToStrHexponential( V ); + break; + default: + break; + } + } +} + +int BadUELs {}; + +std::string GetUELAsString( const int N ) +{ + library::short_string res; + int IDum; + if( !gdxUMUelGet( PGX, N, res.data(), &IDum ) ) + { + BadUELs++; + return "L__" + std::to_string( N ); + } + return res.data(); +} + +std::string GetUel4CSV( const int N ) +{ + return QQCSV( GetUELAsString( N ) ); +} + +bool WriteSymbolAsItem( const int SyNr, const bool DomInfo ) +{ + bool result = true; + library::short_string SyId; + int SyDim, SyTyp; + gdxSymbolInfo( PGX, SyNr, SyId.data(), &SyDim, &SyTyp ); + int SyCnt, SyUser; + library::short_string SyTxt; + gdxSymbolInfoX( PGX, SyNr, &SyCnt, &SyUser, SyTxt.data() ); + fo << '\"' << SyId.data() << "\"." << SyDim << ".\"" << library::gdxDataTypStr( SyTyp ) << '\"'; + if( DomInfo ) + { + std::string Domain; + if( SyDim > 0 ) + { + gdxStrIndex_t DomainArray {}; + gdxStrIndexPtrs_t DomainArrayPtrs; + GDXSTRINDEXPTRS_INIT( DomainArray, DomainArrayPtrs ); + gdxSymbolGetDomainX( PGX, SyNr, DomainArrayPtrs ); + for( int D {}; D < SyDim; D++ ) + { + if( D > 0 ) + Domain += ", "; + Domain += DomainArray[D]; + constexpr int MaxNameLen = 63; + if( static_cast( Domain.length() ) > MaxNameLen ) + { + Domain = "Domain exceeds " + std::to_string( MaxNameLen ) + " characters"; + result = false; + break; + } + } + } + fo << ".\"" << Domain << '\"'; + } + else if( !SyTxt.empty() ) + { + const char qChar = SyTxt.string().find( '\"' ) == std::string::npos ? '\"' : '\''; + fo << ' ' << qChar << SyTxt.data() << qChar; + } + return result; +} + +void WriteSymbolsAsSet( const bool DomInfo ) +{ + if( DomInfo ) + { + fo << "alias (Symbol, Dim, Type, Domain, *);\n" + << "set gdxitemsDI(Symbol,Dim,Type,Domain) Items in the GDX file /\n"; + } + else + { + fo << "alias (Symbol, Dim, Type, *);\n" + << "set gdxitems(Symbol,Dim,Type) Items in the GDX file /\n"; + } + int NrSy, NrUel; + gdxSystemInfo( PGX, &NrSy, &NrUel ); + for( int N { 1 }; N <= NrSy; N++ ) + { + WriteSymbolAsItem( N, DomInfo ); + if( N < NrSy ) + fo << ','; + fo << '\n'; + } + fo << "/;\n"; +} + +void WriteSymbol( const int SyNr ) +{ + library::short_string SyName, S; + std::string SubTypeName; + int ADim, iATyp, ACount, AUser, IDum, NRec, FDim; + gdxSyType ATyp; + bool IsScalar, FrstWrite; + gdxUelIndex_t Keys {}; + gdxValues_t Vals {}; + std::array DefaultValues {}; + + auto WriteItem = [&]( const uint8_t &ValNr ) { + if( !IsScalar && ATyp != dt_set && + ( FilterDef && Vals[ValNr] == DefaultValues[ValNr] ) && + !( ATyp == dt_equ && ( ValNr == GMS_VAL_LOWER || ValNr == GMS_VAL_UPPER ) ) ) + return; + if( FrstWrite ) + FrstWrite = false; + else if( OutFormat == TOutFormat::fmt_gamsbas ) + fo << " ;\n"; + else + { + fo << ", "; + if( !( ( ATyp == dt_var || ATyp == dt_equ ) && ADim == 0 ) ) + fo << '\n'; + } + if( OutFormat == TOutFormat::fmt_gamsbas ) + { + if( LineCount == 6 ) + fo << "$offListing\n"; + fo << ' ' << SyName.data() << '.' << library::valTypStr( ValNr ) << ' '; + } + if( ADim > 0 ) + { + if( OutFormat == TOutFormat::fmt_gamsbas ) + fo << '('; + for( int D {}; D < ADim; D++ ) + { + WriteUEL( GetUELAsString( Keys[D] ) ); + if( D < ADim - 1 ) + fo << Delim; + } + if( OutFormat == TOutFormat::fmt_gamsbas ) + fo << ')'; + } + switch( ATyp ) + { + case dt_set: + if( Vals[GMS_VAL_LEVEL] != 0 ) + { + gdxGetElemText( PGX, static_cast( Vals[GMS_VAL_LEVEL] ), S.data(), &IDum ); + WriteQUELText( S.data() ); + } + break; + case dt_par: + fo << ' '; + WrVal( Vals[ValNr] ); + break; + case dt_equ: + case dt_var: + if( OutFormat == TOutFormat::fmt_gamsbas ) + fo << " = "; + else + { + if( ADim > 0 ) + fo << Delim; + fo << library::valTypStr( ValNr ) << ' '; + } + WrVal( Vals[ValNr] ); + break; + default: + fo << "Oops"; + break; + } + ACount--; + LineCount++; + }; + + auto WriteComments = [&]() { + library::short_string S; + for( int N { 1 }; N <= std::numeric_limits::max(); N++ ) + { + if( gdxSymbolGetComment( PGX, SyNr, N, S.data() ) == 0 ) + break; + fo << "* " << S.data() << '\n'; + } + }; + + gdxUelIndex_t DomainNrs {}; + gdxStrIndex_t DomainIDs {}; + gdxStrIndexPtrs_t DomainIDsPtrs; + GDXSTRINDEXPTRS_INIT( DomainIDs, DomainIDsPtrs ); + library::short_string A2Name, setName; + int A2Dim, iA2Typ, setDim, setType; + + gdxSymbolInfo( PGX, SyNr, SyName.data(), &ADim, &iATyp ); + ATyp = static_cast( iATyp ); + if( ( ATyp == dt_set || ATyp == dt_par ) && OutFormat == TOutFormat::fmt_gamsbas ) + return; + if( ShowHdr ) + fo << '\n'; + // if( false ) + // fo << "$onText\n"; + BadUELs = 0; + IsScalar = ADim == 0 && ATyp == dt_par; + gdxSymbolInfoX( PGX, SyNr, &ACount, &AUser, S.data() ); + // fo << "The sub-type = " << AUser << '\n'; + switch( ATyp ) + { + case dt_set: + DefaultValues[GMS_VAL_LEVEL] = 0; + if( AUser == GMS_SETTYPE_SINGLETON ) + SubTypeName = "Singleton"; + break; + case dt_var: + if( AUser < 0 || AUser > GMS_VARTYPE_SEMIINT ) + AUser = GMS_VARTYPE_FREE; + std::copy( std::begin( gmsDefRecVar[AUser] ), std::end( gmsDefRecVar[AUser] ), std::begin( DefaultValues ) ); + if( AUser != GMS_VARTYPE_UNKNOWN ) + SubTypeName = library::varTypStr( AUser ); + break; + case dt_equ: + if( AUser < GMS_EQUTYPE_E || AUser > GMS_EQUTYPE_B ) + AUser = GMS_EQUTYPE_E; + std::copy( std::begin( gmsDefRecEqu[AUser] ), std::end( gmsDefRecEqu[AUser] ), std::begin( DefaultValues ) ); + if( AUser == GMS_EQUTYPE_B ) + SubTypeName = "Logic"; + break; + default: + DefaultValues[GMS_VAL_LEVEL] = 0; + break; + } + if( ShowHdr ) + { + if( IsScalar ) + fo << "Scalar"; + else + { + if( !SubTypeName.empty() ) + fo << SubTypeName << ' '; + fo << library::gdxDataTypStrL( ATyp ); + } + if( ATyp != dt_alias ) + fo << ' ' << SyName.data(); + else + { + fo << " (" << SyName.data(); + if( AUser == 0 ) + A2Name = "*"; + else + gdxSymbolInfo( PGX, AUser, A2Name.data(), &A2Dim, &iA2Typ ); + fo << ", " << A2Name.data() << ");\n"; + } + if( ATyp != dt_alias ) + { + if( ADim >= 1 ) + { + gdxSymbolGetDomain( PGX, SyNr, DomainNrs ); + for( int D {}; D < ADim; D++ ) + { + if( DomainNrs[D] <= 0 ) + strcpy( DomainIDsPtrs[D], "*" ); + else + { + gdxSymbolInfo( PGX, DomainNrs[D], setName.data(), &setDim, &setType ); + strcpy( DomainIDsPtrs[D], setName.data() ); + } + } + fo << '('; + for( int D {}; D < ADim; D++ ) + { + fo << DomainIDs[D]; + if( D < ADim - 1 ) + fo << ','; + else + fo << ')'; + } + } + if( !S.empty() ) + WriteQText( S.data(), ADim == 0 ); + } + } + if( ACount == 0 && ShowData ) + { + if( IsScalar ) + { + fo << " / 0.0 /;\n"; + WriteComments(); + } + else if( ShowHdr && ATyp != dt_alias ) + { + fo << " / /;\n"; + WriteComments(); + } + } + else + { + if( ShowHdr && ShowData ) + { + fo << " /"; + if( ADim > 0 ) + fo << '\n'; + } + WriteComments(); + if( !ShowData ) + { + if( ATyp != dt_alias ) + { + if( ACount == 0 ) + { + fo << " ; !!empty\n" + << "$loadDC " << SyName.data() << " !!empty\n"; + } + else + { + fo << " ;\n" + << "$loadDC " << SyName.data() << '\n'; + } + } + } + else + { + gdxDataReadRawStart( PGX, SyNr, &NRec ); + FrstWrite = true; + while( gdxDataReadRaw( PGX, Keys, Vals, &FDim ) != 0 ) + { + switch( OutFormat ) + { + case TOutFormat::fmt_gamsbas: + if( ATyp == dt_equ ) + WriteItem( GMS_VAL_MARGINAL ); + else + { + WriteItem( GMS_VAL_LEVEL ); + WriteItem( GMS_VAL_MARGINAL ); + } + break; + case TOutFormat::fmt_csv: + library::assertWithMessage( false, "No CSV processing" ); + break; + case TOutFormat::fmt_normal: + WriteItem( GMS_VAL_LEVEL ); + if( ATyp == dt_var || ATyp == dt_equ ) + { + WriteItem( GMS_VAL_MARGINAL ); + // if( true ) + // { + WriteItem( GMS_VAL_LOWER ); + WriteItem( GMS_VAL_UPPER ); + // } + WriteItem( GMS_VAL_SCALE ); + } + break; + default: + break; + } + } + gdxDataReadDone( PGX ); + if( OutFormat == TOutFormat::fmt_gamsbas ) + fo << " ;\n"; + else if( ShowHdr ) + fo << " /;\n"; + } + } + // if( false ) + // fo << "$offText\n"; + if( BadUELs > 0 ) + fo << "**** " << BadUELs << " reference(s) to unique elements without a string representation"; +} + +int64_t delphiRound( const double x ) +{ + return static_cast( x >= 0 ? x + 0.5 : x - 0.5 ); +} + +void WriteSymbolCSV( const int SyNr ) +{ + int ADim; + gdxStrIndex_t DomS {}; + gdxStrIndexPtrs_t DomSPtrs; + GDXSTRINDEXPTRS_INIT( DomS, DomSPtrs ); + + auto GetDomainNames = [&]() { + gdxStrIndex_t gdxDomS {}; + gdxStrIndexPtrs_t gdxDomSPtrs; + GDXSTRINDEXPTRS_INIT( gdxDomS, gdxDomSPtrs ); + library::short_string s; + bool Done; + int Nr; + + gdxSymbolGetDomainX( PGX, SyNr, gdxDomSPtrs ); + Nr = 1; + for( int D {}; D < ADim; D++ ) + { + s = gdxDomSPtrs[D]; + if( s == "*" ) + { + s = "Dim" + std::to_string( D + 1 ); + strcpy( gdxDomSPtrs[D], s.data() ); + } + while( true ) + { + Done = true; + for( int DD {}; DD < D - 1; DD++ ) + { + if( s == DomSPtrs[DD] ) + { + Done = false; + break; + } + } + if( Done ) + break; + s = std::string { gdxDomS[D] } + '_' + std::to_string( Nr ); + Nr++; + } + strcpy( DomSPtrs[D], s.data() ); + } + }; + + library::short_string SyName, S; + int iATyp, NRec, FDim, IDum, Col, ColCnt, NrSymb, NrUEL, HighIndex, Indx; + gdxSyType ATyp; + std::unique_ptr CSVCols; + std::unique_ptr CSVUels; + gdxUelIndex_t Keys {}; + gdxValues_t Vals {}; + + BadUELs = 0; + gdxSystemInfo( PGX, &NrSymb, &NrUEL ); + gdxSymbolInfo( PGX, SyNr, SyName.data(), &ADim, &iATyp ); + ATyp = static_cast( iATyp ); + GetDomainNames(); + if( ADim < 2 ) + CDim = false; + if( !CDim ) + { + if( bHeader ) + { + if( !Header.empty() ) + fo << Header << '\n'; + } + else + { + if( ShowHdr ) + { + for( int D {}; D < ADim; D++ ) + { + fo << QQCSV( DomS[D] ); + if( D < ADim - 1 ) + fo << Delim; + } + if( !( ATyp == dt_set || ATyp == dt_alias ) ) + { + if( ADim == 0 ) + fo << QQCSV( "Val" ); + else + fo << Delim << QQCSV( "Val" ); + } + else if( bCSVSetText ) + fo << Delim << QQCSV( "Text" ); + if( ( ATyp == dt_var || ATyp == dt_equ ) && bFullEVRec ) + { + fo << Delim << QQCSV( "Marginal" ) + << Delim << QQCSV( "Lower" ) + << Delim << QQCSV( "Upper" ) + << Delim << QQCSV( "Scale" ); + } + fo << '\n'; + } + } + gdxDataReadRawStart( PGX, SyNr, &NRec ); + while( gdxDataReadRaw( PGX, Keys, Vals, &FDim ) != 0 ) + { + if( ADim == 0 ) + { + WrVal( Vals[GMS_VAL_LEVEL] ); + if( ( ATyp == dt_var || ATyp == dt_equ ) && bFullEVRec ) + { + fo << Delim; + WrVal( Vals[GMS_VAL_MARGINAL] ); + fo << Delim; + WrVal( Vals[GMS_VAL_LOWER] ); + fo << Delim; + WrVal( Vals[GMS_VAL_UPPER] ); + fo << Delim; + WrVal( Vals[GMS_VAL_SCALE] ); + } + } + else + for( int D {}; D < ADim; D++ ) + { + fo << GetUel4CSV( Keys[D] ); + if( D < ADim - 1 ) + fo << Delim; + else if( !( ATyp == dt_set || ATyp == dt_alias ) ) + { + fo << Delim; + WrVal( Vals[GMS_VAL_LEVEL] ); + if( ( ATyp == dt_var || ATyp == dt_equ ) && bFullEVRec ) + { + fo << Delim; + WrVal( Vals[GMS_VAL_MARGINAL] ); + fo << Delim; + WrVal( Vals[GMS_VAL_LOWER] ); + fo << Delim; + WrVal( Vals[GMS_VAL_UPPER] ); + fo << Delim; + WrVal( Vals[GMS_VAL_SCALE] ); + } + } + else if( bCSVSetText ) + { + if( Vals[GMS_VAL_LEVEL] != 0 ) + { + gdxGetElemText( PGX, static_cast( delphiRound( Vals[GMS_VAL_LEVEL] ) ), S.data(), &IDum ); + fo << Delim << QQCSV( S.data() ); + } + else + fo << Delim; + } + } + fo << '\n'; + } + } + else + { + CSVCols = std::make_unique( NrUEL ); + // TODO: Check this, could be redundant: + std::fill_n( CSVCols.get(), NrUEL, false ); + HighIndex = 0; + ColCnt = 0; + gdxDataReadRawStart( PGX, SyNr, &NRec ); + while( gdxDataReadRaw( PGX, Keys, Vals, &FDim ) != 0 ) + { + Indx = Keys[ADim - 1]; + if( !CSVCols[Indx - 1] ) + { + CSVCols[Indx - 1] = true; + ColCnt++; + if( Indx > HighIndex ) + HighIndex = Indx; + } + } + gdxDataReadDone( PGX ); + + CSVUels = std::make_unique( ColCnt ); + // TODO: Check this, could be redundant: + std::fill_n( CSVUels.get(), ColCnt, 0 ); + Col = 0; + for( Indx = 0; Indx < HighIndex; Indx++ ) + { + if( CSVCols[Indx] ) + { + CSVUels[Col] = Indx + 1; + Col++; + } + } + library::assertWithMessage( Col == ColCnt, "Col != ColCnt" ); + if( bHeader ) + { + if( !Header.empty() ) + fo << Header << '\n'; + } + else if( ShowHdr ) + { + for( int D {}; D < ADim - 1; D++ ) + fo << QQCSV( DomS[D] ) << Delim; + for( Col = 0; Col < ColCnt; Col++ ) + { + fo << GetUel4CSV( CSVUels[Col] ); + if( Col < ColCnt - 1 ) + fo << Delim; + } + fo << '\n'; + } + gdxDataReadRawStart( PGX, SyNr, &NRec ); + bool EoFData = gdxDataReadRaw( PGX, Keys, Vals, &FDim ) == 0; + while( !EoFData ) + { + for( int D {}; D < ADim - 1; D++ ) + { + fo << GetUel4CSV( Keys[D] ); + if( D < ADim - 2 ) + fo << Delim; + } + Col = -1; + do { + Indx = Keys[ADim - 1]; + while( Col < ColCnt ) + { + Col++; + if( CSVUels[Col] >= Indx ) + break; + fo << Delim; + } + if( CSVUels[Col] == Indx ) + { + fo << Delim; + if( ATyp == dt_set || ATyp == dt_alias ) + fo << 'Y'; + else + WrVal( Vals[GMS_VAL_LEVEL] ); + EoFData = gdxDataReadRaw( PGX, Keys, Vals, &FDim ) == 0; + if( FDim < ADim || EoFData ) + { + while( Col < ColCnt - 1 ) + { + fo << Delim; + Col++; + } + break; + } + } + } while( true ); + fo << '\n'; + } + gdxDataReadDone( PGX ); + } + if( BadUELs > 0 ) + fo << "**** " << BadUELs << " reference(s) to unique elements without a string representation\n"; +} + +void WriteSymbolInfo() +{ + int ADim, iATyp, NrSy, NrUel, w1, w2, w3, ACount, AUserInfo; + library::short_string AName, AExplText; + std::map SL; + + gdxSystemInfo( PGX, &NrSy, &NrUel ); + w1 = gdlib::strutilx::IntegerWidth( NrSy ); + w2 = static_cast( "Symbol"s.length() ); + w3 = static_cast( "Records"s.length() ); + for( int N { 1 }; N <= NrSy; N++ ) + { + gdxSymbolInfo( PGX, N, AName.data(), &ADim, &iATyp ); + gdxSymbolInfoX( PGX, N, &ACount, &AUserInfo, AExplText.data() ); + if( static_cast( AName.length() ) > w2 ) + w2 = AName.length(); + if( gdlib::strutilx::IntegerWidth( ACount ) > w3 ) + w3 = gdlib::strutilx::IntegerWidth( ACount ); + SL.insert( { AName, N } ); + } + fo << gdlib::strutilx::PadLeft( " ", w1 ) << ' ' + << gdlib::strutilx::PadRight( "Symbol", w2 ) + << " Dim" << " Type " + << gdlib::strutilx::PadRight( "Records", w3 ) + << " " << "Explanatory text\n"; + + int N {}; + for( const auto &pair: SL ) + { + gdxSymbolInfo( PGX, pair.second, AName.data(), &ADim, &iATyp ); + gdxSymbolInfoX( PGX, pair.second, &ACount, &AUserInfo, AExplText.data() ); + fo << gdlib::strutilx::PadLeft( std::to_string( N + 1 ), w1 ) << ' ' << gdlib::strutilx::PadRight( AName.data(), w2 ) << ' ' + << gdlib::strutilx::PadLeft( std::to_string( ADim ), 3 ) << ' ' << gdlib::strutilx::PadLeft( library::gdxDataTypStr( iATyp ), 4 ) << ' ' + << gdlib::strutilx::PadLeft( std::to_string( ACount ), w3 ) << " " << AExplText.data() << '\n'; + N++; + } +} + +void WriteDomainInfo() +{ + constexpr std::array StrDInfo { "N/A", "None", "Relaxed", "Regular" }; + + library::short_string AName; + int ADim, iATyp, NrSy, NrUel, w1, dinfo; + std::map SL; + gdxStrIndex_t DomainIDs {}; + gdxStrIndexPtrs_t DomainIDsPtrs; + GDXSTRINDEXPTRS_INIT( DomainIDs, DomainIDsPtrs ); + + gdxSystemInfo( PGX, &NrSy, &NrUel ); + w1 = gdlib::strutilx::IntegerWidth( NrSy ); + if( w1 < 4 ) + w1 = 4; + fo << gdlib::strutilx::PadLeft( "SyNr", w1 ) + << " Type" << " DomInf " << "Symbol\n"; + for( int N { 1 }; N <= NrSy; N++ ) + { + gdxSymbolInfo( PGX, N, AName.data(), &ADim, &iATyp ); + SL.insert( { AName, N } ); + } + for( const auto &pair: SL ) + { + gdxSymbolInfo( PGX, pair.second, AName.data(), &ADim, &iATyp ); + fo << gdlib::strutilx::PadLeft( std::to_string( pair.second ), w1 ) << ' ' << gdlib::strutilx::PadLeft( library::gdxDataTypStr( iATyp ), 5 ); + dinfo = gdxSymbolGetDomainX( PGX, pair.second, DomainIDsPtrs ); + fo << ' ' << gdlib::strutilx::PadLeft( StrDInfo[dinfo], 7 ) << ' ' << AName.data(); + if( ADim > 0 ) + { + fo << '('; + for( int D {}; D < ADim; D++ ) + { + if( D > 0 ) + fo << ", "; + fo << DomainIDs[D]; + } + fo << ')'; + } + fo << '\n'; + } +} + +// Write out the set text in the GDX file +void WriteSetText() +{ + int nText, lo, hi, mid, idummy, rc, textIdx, mxTextIdx, nSyms, symDim, symTyp, nRecs, fDim; + library::short_string s; + gdxUelIndex_t keys {}; + gdxValues_t vals {}; + + lo = 0; + rc = gdxGetElemText( PGX, lo, s.data(), &idummy ); + library::assertWithMessage( 1 == rc, "Did not find text in position 0" ); + hi = std::numeric_limits::max(); + rc = gdxGetElemText( PGX, hi, s.data(), &idummy ); + library::assertWithMessage( 0 == rc, "Found text in position high(integer)" ); + while( lo + 1 < hi ) + { + mid = lo + ( hi - lo ) / 2; + rc = gdxGetElemText( PGX, mid, s.data(), &idummy ); + if( 1 == rc ) + lo = mid; + else + hi = mid; + } + library::assertWithMessage( lo + 1 == hi, "Impossible end to binary search in WriteSetText" ); + nText = lo + 1; + + // Now find the max textIdx stored for any set element + mxTextIdx = 0; + gdxSystemInfo( PGX, &nSyms, &idummy ); + for( int iSym {}; iSym < nSyms; iSym++ ) + { + gdxSymbolInfo( PGX, iSym, s.data(), &symDim, &symTyp ); + if( static_cast( symTyp ) != dt_set ) + continue; + // fo << "Found set " << s << '\n'; + gdxDataReadRawStart( PGX, iSym, &nRecs ); + while( gdxDataReadRaw( PGX, keys, vals, &fDim ) != 0 ) + { + textIdx = static_cast( delphiRound( vals[GMS_VAL_LEVEL] ) ); + if( mxTextIdx < textIdx ) + mxTextIdx = textIdx; + } + gdxDataReadDone( PGX ); + } + // fo << '\n'; + fo << "Count of set text strings in GDX: " << nText << '\n' + << "max 0-based textIdx found in GDX: " << mxTextIdx << '\n' + << " idx text\n"; + for( textIdx = 0; textIdx < nText; textIdx++ ) + { + if( textIdx == 0 ) + s.clear(); + else + gdxGetElemText( PGX, textIdx, s.data(), &idummy ); + fo << gdlib::strutilx::PadLeft( std::to_string( textIdx ), 6 ) << " \"" << s.data() << '\"' << '\n'; + } +} + +void Usage( const library::AuditLine &AuditLine ) +{ + std::cout << "gdxdump: Write GDX file in ASCII\n" + << AuditLine.getAuditLine() << "\n\n" + << "Usage:\n" + << "gdxdump \n" + << "\n" + << " -V or -Version Write version info of input file only\n" + << " Output= Write output to file\n" + << " Symb= Select a single identifier\n" + << " UelTable= Include all unique elements\n" + << " Delim=[period, comma, tab, blank, semicolon]\n" + << " Specify a dimension delimiter\n" + << " DecimalSep=[period, comma]\n" + << " Specify a decimal separator\n" + << " NoHeader Suppress writing of the headers\n" + << " NoData Write headers only; no data\n" + << " CSVAllFields When writing CSV write all variable/equation fields\n" + << " CSVSetText When writing CSV write set element text\n" + << " Symbols Get a list of all symbols\n" + << " DomainInfo Get a list of all symbols showing domain information\n" + << " SymbolsAsSet Get a list of all symbols as data for a set\n" + << " SymbolsAsSetDI Get a list of all symbols as data for a set includes domain information\n" + << " SetText Show the list of set text (aka associated text)\n" + << " Format=[normal, gamsbas, csv]\n" + << " dFormat=[normal, hexponential, hexBytes]\n" + << " CDim=[Y, N] Use last dimension as column headers\n" + << " (for CSV format only; default=N)\n" + << " FilterDef=[Y, N] Filter default values; default=Y\n" + << " EpsOut= String to be used when writing the value for EPS; default=EPS\n" + << " NaOut= String to be used when writing the value for Not Available; default=NA\n" + << " PinfOut= String to be used when writing the value for Positive Infinity; default=+Inf\n" + << " MinfOut= String to be used when writing the value for Negative Infinity; default=-Inf\n" + << " UndfOut= String to be used when writing the value for Undefined; default=Undf\n" + << " ZeroOut= String to be used when writing the value for Zero; default=0\n" + << " Header= New header for CSV output format" << std::endl; +} + +int ParamCount, ParamNr; +const char **ParamStr; +std::string ParamHold; + +std::string NextParam() +{ + std::string result; + + if( !ParamHold.empty() ) + { + result = ParamHold; + ParamHold.clear(); + } + else if( ParamNr <= ParamCount ) + { + result = ParamStr[ParamNr]; + ParamNr++; + size_t k = result.find( '=' ); + if( k != std::string::npos ) + { + ParamHold = result.substr( k + 1 ); + // Keep the '=': + result.erase( k + 1 ); + } + else if( ParamNr <= ParamCount ) + { + std::string nextParam = ParamStr[ParamNr]; + if( nextParam.length() > 0 && nextParam.front() == '=' ) + { + result += '='; + ParamHold = nextParam.substr( 1 ); + ParamNr++; + } + } + } + if( result == "\'\'" ) + return {}; + return result; +} + +void WriteAcronyms() +{ + int Cnt, Indx; + library::short_string sName, sText; + + Cnt = gdxAcronymCount( PGX ); + if( Cnt <= 0 ) + return; + + fo << '\n'; + for( int N {}; N < Cnt; N++ ) + { + gdxAcronymGetInfo( PGX, N, sName.data(), sText.data(), &Indx ); + fo << "Acronym " << sName.data(); + if( !sText.empty() ) + WriteQText( sText.data(), true ); + fo << ';' << '\n'; + } +} + +int main( const int argc, const char *argv[] ) +{ + library::short_string s, Symb; + std::string InputFile, DLLStr, UELSetName, OutputName; + int ErrNr, ExitCode; + bool ListAllSymbols, ListSymbolsAsSet, ListSymbolsAsSetDI, UsingIDE, VersionOnly, DomainInfo, showSetText; + + // for( int N {}; N < argc; N++ ) + // std::cout << "Parameter " << N << ": |" << argv[N] << '|' << std::endl; + + ParamCount = argc - 1; + ParamStr = argv; + + library::AuditLine AuditLine { "GDXDUMP" }; + if( ParamCount > 0 && gdlib::strutilx::StrUEqual( ParamStr[1], "AUDIT" ) ) + { + std::cout << AuditLine.getAuditLine() << std::endl; + return 0; + } + + Delim = '\0'; + DecimalSep = '\0'; + ShowHdr = true; + ShowData = true; + ListAllSymbols = false; + ListSymbolsAsSet = false; + ListSymbolsAsSetDI = false; + DomainInfo = false; + showSetText = false; + OutFormat = TOutFormat::fmt_none; + CDim = false; + dblFormat = TDblFormat::dbl_none; + UsingIDE = false; + ExitCode = 0; + VersionOnly = false; + FilterDef = true; + bEpsOut = false; + bNaOut = false; + bPinfOut = false; + bMinfOut = false; + bUndfOut = false; + bZeroOut = false; + bHeader = false; + bFullEVRec = false; + bCSVSetText = false; + + if( ParamCount == 0 ) + { + // show usage + ExitCode = 1; + } + else + { + InputFile = ParamStr[1]; + ParamNr = 2; + ParamHold.clear(); + + while( ParamNr <= ParamCount ) + { + s = NextParam(); + s.to_upper_case(); + if( s == "SYMB" || s == "SYMB=" ) + { + if( !Symb.empty() ) + { + library::printErrorMessage( "Only one single symbol can be specified" ); + ExitCode = 1; + break; + } + Symb = NextParam(); + if( Symb.empty() ) + { + library::printErrorMessage( "Symbol missing" ); + ExitCode = 1; + break; + } + continue; + } + if( s == "UELTABLE" || s == "UELTABLE=" ) + { + if( !UELSetName.empty() ) + { + library::printErrorMessage( "Only one name for the UEL table can be specified" ); + ExitCode = 1; + break; + } + UELSetName = NextParam(); + if( UELSetName.empty() ) + { + library::printErrorMessage( "UELSetName missing" ); + ExitCode = 1; + break; + } + continue; + } + if( s == "DELIM" || s == "DELIM=" ) + { + if( Delim != '\0' ) + { + library::printErrorMessage( "Only one delimiter can be specified" ); + ExitCode = 1; + break; + } + s = NextParam(); + s.to_upper_case(); + if( s == "TAB" ) + Delim = '\t'; + else if( s == "COMMA" ) + Delim = ','; + else if( s == "PERIOD" ) + Delim = '.'; + else if( s == "BLANK" ) + Delim = ' '; + else if( s == "SEMICOLON" ) + Delim = ';'; + else + { + library::printErrorMessage( "Unrecognized delimiter" ); + ExitCode = 1; + break; + } + continue; + } + if( s == "DECIMALSEP" || s == "DECIMALSEP=" ) + { + if( DecimalSep != '\0' ) + { + library::printErrorMessage( "Only one Decimal separator character can be specified" ); + ExitCode = 1; + break; + } + s = NextParam(); + s.to_upper_case(); + if( s == "PERIOD" ) + DecimalSep = '.'; + else if( s == "COMMA" ) + DecimalSep = ','; + else + { + library::printErrorMessage( "Unrecognized Decimal separator character" ); + ExitCode = 1; + break; + } + continue; + } + if( s == "SYMBOLS" ) + { + ListAllSymbols = true; + continue; + } + if( s == "DOMAININFO" ) + { + DomainInfo = true; + continue; + } + if( s == "SETTEXT" ) + { + showSetText = true; + continue; + } + if( s == "SYMBOLSASSET" ) + { + ListSymbolsAsSet = true; + continue; + } + if( s == "SYMBOLSASSETDI" ) + { + ListSymbolsAsSetDI = true; + continue; + } + if( s == "NOHEADER" ) + { + ShowHdr = false; + continue; + } + if( s == "NODATA" ) + { + ShowData = false; + continue; + } + if( s == "CSVALLFIELDS" ) + { + bFullEVRec = true; + continue; + } + if( s == "CSVSETTEXT" ) + { + bCSVSetText = true; + continue; + } + if( s == "FORMAT" || s == "FORMAT=" ) + { + if( OutFormat != TOutFormat::fmt_none ) + { + library::printErrorMessage( "Only one format can be specified" ); + ExitCode = 1; + break; + } + s = NextParam(); + s.to_upper_case(); + if( s == "NORMAL" ) + OutFormat = TOutFormat::fmt_normal; + else if( s == "GAMSBAS" ) + OutFormat = TOutFormat::fmt_gamsbas; + else if( s == "CSV" ) + OutFormat = TOutFormat::fmt_csv; + else + { + library::printErrorMessage( "Unrecognized format" ); + ExitCode = 1; + break; + } + continue; + } + if( s == "DFORMAT" || s == "DFORMAT=" ) + { + if( dblFormat != TDblFormat::dbl_none ) + { + library::printErrorMessage( "Only one dformat can be specified" ); + ExitCode = 1; + break; + } + s = NextParam(); + s.to_upper_case(); + if( s == "NORMAL" ) + dblFormat = TDblFormat::dbl_none; + else if( s == "HEXBYTES" ) + dblFormat = TDblFormat::dbl_hexBytes; + else if( s == "HEXPONENTIAL" ) + dblFormat = TDblFormat::dbl_hexponential; + else + { + library::printErrorMessage( "Unrecognized dformat" ); + ExitCode = 1; + break; + } + continue; + } + if( s == "OUTPUT" || s == "OUTPUT=" ) + { + if( !OutputName.empty() ) + { + library::printErrorMessage( "Only one output file can be specified" ); + ExitCode = 1; + break; + } + OutputName = NextParam(); + if( OutputName.empty() ) + { + library::printErrorMessage( "Output file missing" ); + ExitCode = 1; + break; + } + continue; + } + if( s == "IDE" || s == "IDE=" ) + { + s = NextParam(); + if( s.empty() ) + { + library::printErrorMessage( "Value missing for IDE parameter" ); + ExitCode = 1; + break; + } + UsingIDE = s.front() == 'Y' || s.front() == 'y' || s.front() == '1'; + continue; + } + if( s == "FILTERDEF" || s == "FILTERDEF=" ) + { + s = NextParam(); + if( s.empty() ) + { + library::printErrorMessage( "Value missing for FilterDef parameter" ); + ExitCode = 1; + break; + } + FilterDef = s.front() == 'Y' || s.front() == 'y' || s.front() == '1'; + continue; + } + if( s == "CDIM" || s == "CDIM=" ) + { + s = NextParam(); + if( s.empty() ) + { + library::printErrorMessage( "Value missing for CDim parameter" ); + ExitCode = 1; + break; + } + CDim = s.front() == 'Y' || s.front() == 'y' || s.front() == '1'; + continue; + } + if( s == "-V" || s == "-VERSION" ) + { + VersionOnly = true; + continue; + } + if( s == "EPSOUT" || s == "EPSOUT=" ) + { + EpsOut = NextParam(); + bEpsOut = true; + continue; + } + if( s == "NAOUT" || s == "NAOUT=" ) + { + NaOut = NextParam(); + bNaOut = true; + continue; + } + if( s == "PINFOUT" || s == "PINFOUT=" ) + { + PinfOut = NextParam(); + bPinfOut = true; + continue; + } + if( s == "MINFOUT" || s == "MINFOUT=" ) + { + MinfOut = NextParam(); + bMinfOut = true; + continue; + } + if( s == "UNDFOUT" || s == "UNDFOUT=" ) + { + UndfOut = NextParam(); + bUndfOut = true; + continue; + } + if( s == "ZEROOUT" || s == "ZEROOUT=" ) + { + ZeroOut = NextParam(); + bZeroOut = true; + continue; + } + if( s == "HEADER" || s == "HEADER=" ) + { + Header = NextParam(); + bHeader = true; + continue; + } + library::printErrorMessage( "Unrecognized option: " + s.string() ); + ExitCode = 1; + break; + } + } + + // The following line has been moved to fix errors with gotos + std::filesystem::path InputFilePath( InputFile ); + + if( ExitCode != 0 ) + { + Usage( AuditLine ); + goto End; + } + + if( OutFormat == TOutFormat::fmt_none ) + OutFormat = TOutFormat::fmt_normal; + if( dblFormat == TDblFormat::dbl_none ) + dblFormat = TDblFormat::dbl_normal; + if( Delim == '\0' ) + { + if( OutFormat == TOutFormat::fmt_csv ) + Delim = ','; + else + Delim = '.'; + } + if( DecimalSep == '\0' ) + DecimalSep = '.'; + if( OutFormat == TOutFormat::fmt_csv ) + ShowData = true; + if( Delim == DecimalSep && Delim != '.' ) + { + library::printErrorMessage( "Delimiter and Decimal separator characters should be different" ); + ExitCode = 1; + goto End; + } + if( OutFormat == TOutFormat::fmt_csv && Symb.empty() ) + { + library::printErrorMessage( "Symbol not specified when writing a CSV file" ); + ExitCode = 1; + goto End; + } + + if( !std::filesystem::exists( InputFilePath ) && InputFilePath.extension().string().empty() ) + { + InputFilePath.replace_extension( std::filesystem::path( "gdx" ) ); + InputFile = InputFilePath.string(); + } + if( !std::filesystem::exists( InputFilePath ) ) + { + library::printErrorMessage( "GDX file not found: " + InputFile ); + ExitCode = 2; + goto End; + } + + if( Symb.empty() ) + ShowHdr = true; + if( OutFormat == TOutFormat::fmt_gamsbas ) + { + ShowHdr = false; + ShowData = true; + } + + { + library::short_string error_message; + + if( !gdxGetReady( error_message.data(), error_message.length() ) ) + { + library::printErrorMessage( "Error loading GDX library: " + error_message.string() ); + ExitCode = 3; + goto End; + } + + if( !gdxCreate( &PGX, error_message.data(), error_message.length() ) ) + { + library::printErrorMessage( "Error using GDX library: " + error_message.string() ); + ExitCode = 3; + goto End; + } + } + + gdxOpenRead( PGX, InputFile.data(), &ErrNr ); + if( ErrNr != 0 ) + { + gdxErrorStr( PGX, ErrNr, s.data() ); + library::printErrorMessage( "Problem reading GDX file: " + s.string() ); + ExitCode = 4; + goto End; + } + + ErrNr = gdxGetLastError( PGX ); + if( ErrNr != 0 ) + { + gdxErrorStr( PGX, ErrNr, s.data() ); + library::printErrorMessage( "Problem reading GDX file: " + s.string() ); + ExitCode = 5; + goto End; + } + + if( VersionOnly ) + { + library::short_string FileStr, ProduceStr; + gdxFileVersion( PGX, FileStr.data(), ProduceStr.data() ); + int NrSy, NrUel, FileVer, ComprLev; + gdxSystemInfo( PGX, &NrSy, &NrUel ); + gdxFileInfo( PGX, &FileVer, &ComprLev ); + std::cout << "* File version : " << FileStr.data() << '\n' + << "* Producer : " << ProduceStr.data() << '\n' + << "* File format : " << gdlib::strutilx::IntToNiceStrW( FileVer, 4 ).data() << '\n' + << "* Compression : " << gdlib::strutilx::IntToNiceStrW( ComprLev, 4 ).data() << '\n' + << "* Symbols : " << gdlib::strutilx::IntToNiceStrW( NrSy, 4 ).data() << '\n' + << "* Unique Elements: " << gdlib::strutilx::IntToNiceStrW( NrUel, 4 ).data() << std::endl; + goto AllDone; + } + + if( !OutputName.empty() ) + { + OutputFile.open( OutputName ); + if( !OutputFile.is_open() ) + { + ErrNr = errno; + library::printErrorMessage( "Error opening output file: " + OutputName ); + library::printErrorMessage( "Error: " + std::to_string( ErrNr ) + " = " + strerror( errno ) ); + ExitCode = 6; + goto End; + } + fo.rdbuf( OutputFile.rdbuf() ); + } + + if( !ShowData ) + { + fo << '\n' + << "$gdxIn " << InputFile << '\n'; + } + if( OutFormat != TOutFormat::fmt_csv ) + WriteAcronyms(); + if( !UELSetName.empty() ) + { + if( OutFormat == TOutFormat::fmt_csv ) + { + WriteUELTable( {} ); + goto AllDone; + } + fo << '\n'; + WriteUELTable( UELSetName ); + } + if( ListAllSymbols ) + { + WriteSymbolInfo(); + goto AllDone; + } + if( ListSymbolsAsSet ) + { + WriteSymbolsAsSet( false ); + goto AllDone; + } + if( ListSymbolsAsSetDI ) + { + WriteSymbolsAsSet( true ); + goto AllDone; + } + if( DomainInfo ) + { + WriteDomainInfo(); + goto AllDone; + } + if( showSetText ) + { + WriteSetText(); + goto AllDone; + } + + LineCount = 0; + if( !Symb.empty() ) + { + int N; + if( gdxFindSymbol( PGX, Symb.data(), &N ) != 0 ) + { + if( OutFormat == TOutFormat::fmt_csv ) + WriteSymbolCSV( N ); + else + { + WriteSymbol( N ); + if( !ShowHdr ) + fo << '\n'; + } + } + else + { + fo << "Symbol not found: " << Symb.data() << '\n'; + ExitCode = 6; + } + } + else + { + if( OutFormat == TOutFormat::fmt_normal ) + ShowHdr = true; + fo << "$onEmpty\n"; + if( !ShowData ) + { + fo << "$offEolCom\n" + << "$eolCom !!\n"; + } + int NrSy, NrUel; + gdxSystemInfo( PGX, &NrSy, &NrUel ); + for( int N { 1 }; N <= NrSy; N++ ) + WriteSymbol( N ); + fo << '\n' + << "$offEmpty\n"; + } + if( OutFormat == TOutFormat::fmt_gamsbas && LineCount > 6 ) + fo << "$onListing\n"; + +AllDone: + gdxClose( PGX ); + gdxFree( &PGX ); + + if( UsingIDE && !OutputName.empty() ) + std::cout << "GDXDump file written: " << OutputName << "[FIL:\"" << OutputName << "\",0,0]" << std::endl; + +End: + if( OutputFile.is_open() ) + OutputFile.close(); + + return ExitCode; +} + +}// namespace gdxdump + +int main( const int argc, const char *argv[] ) +{ + return gdxdump::main( argc, argv ); +} diff --git a/src/tools/gdxdump/gdxdump.h b/src/tools/gdxdump/gdxdump.h new file mode 100644 index 0000000..b787b76 --- /dev/null +++ b/src/tools/gdxdump/gdxdump.h @@ -0,0 +1,81 @@ +#ifndef GDX_GDXDUMP_H +#define GDX_GDXDUMP_H + +#include +#include +#include +#include + +#include "../library/common.h" + +namespace gdxdump +{ + +enum class TOutFormat : uint8_t +{ + fmt_none, + fmt_normal, + fmt_gamsbas, + fmt_csv +}; + +enum class TDblFormat : uint8_t +{ + dbl_none, + dbl_normal, + dbl_hexponential, + dbl_hexBytes +}; + +char QQ( const std::string &s ); + +std::string QQCSV( const std::string &s ); + +// Function is commented out in the Delphi source code +// std::string QUEL(const std::string &S); + +// Auxiliary function for WriteQText and WriteQUELText +void WriteQuotedCommon( const std::string &S, const std::function &isSpecialPredicate, int i = {}, bool G = true ); + +void WriteQText( const std::string &S, bool checkParenthesis ); + +void WriteQUELText( const std::string &S ); + +void WriteUEL( const std::string &S ); + +void WriteUELTable( const std::string &name ); + +void WrVal( double V ); + +std::string GetUELAsString( int N ); + +std::string GetUel4CSV( int N ); + +bool WriteSymbolAsItem( int SyNr, bool DomInfo ); + +void WriteSymbolsAsSet( bool DomInfo ); + +void WriteSymbol( int SyNr ); + +// Auxiliary function for WriteSymbolCSV and WriteSetText +int64_t delphiRound( double x ); + +void WriteSymbolCSV( int SyNr ); + +void WriteSymbolInfo(); + +void WriteDomainInfo(); + +void WriteSetText(); + +void Usage(const library::AuditLine &AuditLine); + +std::string NextParam(); + +void WriteAcronyms(); + +int main( int argc, const char *argv[] ); + +}// namespace gdxdump + +#endif//GDX_GDXDUMP_H diff --git a/src/tools/gdxmerge/gdxmerge.cpp b/src/tools/gdxmerge/gdxmerge.cpp new file mode 100644 index 0000000..d61aa8c --- /dev/null +++ b/src/tools/gdxmerge/gdxmerge.cpp @@ -0,0 +1,870 @@ +/** + * GAMS - General Algebraic Modeling System C++ API + * + * Copyright (c) 2017-2024 GAMS Software GmbH + * Copyright (c) 2017-2024 GAMS Development Corp. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include "gdxmerge.h" +#include "../library/cmdpar.h" +#include "../../gdlib/utils.h" +#include "../../gdlib/strutilx.h" +#include "../../rtl/sysutils_p3.h" + +// TODO: Disable at some point? +#define OLD_MEMORY_CHECK + +namespace gdxmerge +{ + +bool DoBigSymbols, StrictMode; +int64_t SizeCutOff; +library::short_string OutFile; +std::vector FilePatterns; +gdxHandle_t PGXMerge; +unsigned int InputFilesRead; +std::unique_ptr SyList; + +TGAMSSymbol::TGAMSSymbol( const int ADim, const gdxSyType AType, const int ASubTyp ) + : SyDim( ADim ), SySubTyp( ASubTyp ), SyTyp( AType ), + SyData( std::make_unique>( ADim, DataTypSize.at( AType ) * sizeof( double ) ) ) +{} + +TGDXFileEntry::TGDXFileEntry( const std::string &AFileName, const std::string &AFileId, const std::string &AFileInfo ) + : FFileName( AFileName ), FFileId( AFileId ), FFileInfo( AFileInfo ) +{} + +template +TFileList::~TFileList() +{ + Clear(); +} + +template +void TFileList::Clear() +{ + for( int i {}; i < this->GetCount(); i++ ) + delete( *this )[i]; + gdlib::gmsobj::TXList::Clear(); +} + +template +void TFileList::AddFile( const std::string &AFileName, const std::string &AFileId, const std::string &AFileInfo ) +{ + gdlib::gmsobj::TXList::Add( new TGDXFileEntry( AFileName, AFileId, AFileInfo ) ); +} + +template +std::string TFileList::FileName( const int Index ) +{ + return gdlib::gmsobj::TXList::GetConst( Index )->FFileName; +} + +template +std::string TFileList::FileId( const int Index ) +{ + return gdlib::gmsobj::TXList::GetConst( Index )->FFileId; +} + +template +std::string TFileList::FileInfo( const int Index ) +{ + return gdlib::gmsobj::TXList::GetConst( Index )->FFileInfo; +} + +TSymbolList::TSymbolList() + : gdlib::gmsobj::TXHashedStringList() +{ + StrPool = std::make_unique>(); + const library::short_string empty_string; + StrPool->Add( empty_string.data(), empty_string.length() ); + FileList = std::make_unique>(); + library::short_string Msg; + gdxCreate( &PGXMerge, Msg.data(), Msg.length() ); +} + +TSymbolList::~TSymbolList() +{ + Clear(); +} + +// TODO: AS: The whole TX*List destruction and element deletion mechanism is not ideal now +// and requires duplicated code (see TFileList destructor and clear) +void TSymbolList::Clear() +{ + for( int i {}; i < FCount; i++ ) + delete GetObject( i ); + TXHashedStringList::Clear(); +} + +void TSymbolList::OpenOutput( const library::short_string &AFileName, int &ErrNr ) +{ + gdxOpenWrite( PGXMerge, AFileName.data(), "gdxmerge", &ErrNr ); + gdxStoreDomainSetsSet( PGXMerge, false ); +} + +int TSymbolList::AddUEL( const library::short_string &S ) +{ + int result; + gdxUELRegisterStr( PGXMerge, S.data(), &result ); + return result; +} + +int TSymbolList::AddSymbol( const std::string &AName, const int ADim, const gdxSyType AType, const int ASubTyp ) +{ + auto is_in_list = []( const std::vector &list, const std::string &value ) { + return std::find( list.begin(), list.end(), value ) != list.end(); + }; + + if( ( !IncludeList.empty() && !is_in_list( IncludeList, AName ) ) || + ( !ExcludeList.empty() && is_in_list( ExcludeList, AName ) ) ) + return -1; + + return TXHashedStringList::AddObject( AName.data(), AName.length(), new TGAMSSymbol( ADim, AType, ASubTyp ) ); +} + +void TSymbolList::AddPGXFile( const int FNr, const TProcessPass Pass ) +{ + bool FrstError; + library::short_string SyName, FileName; + + auto CheckError = [&]( const bool Cnd, const std::string &Msg ) -> bool { + bool Result { !Cnd }; + if( Result ) + { + FErrorCount++; + if( FrstError ) + { + // TODO: Use four stars here instead of the usual three? + library::printErrorMessage( "\n**** Error in file " + FileName.string() ); + FrstError = false; + } + library::printErrorMessage( " " + Msg + ": " + SyName.string() ); + } + return Result; + }; + + gdxHandle_t PGX; + int NrSy, NrUel, N, Dim, SyITyp, SyIndx, NrRecs, FDim, D, INode, SySubTyp, DummyCount, ErrNr, RecLen; + gdxSyType SyTyp; + TGAMSSymbol *SyObj; + gdxStrIndex_t IndxS {}; + gdxStrIndexPtrs_t IndxSPtrs; + GDXSTRINDEXPTRS_INIT( IndxS, IndxSPtrs ); + gdxUelIndex_t IndxI {}; + gdxValues_t Vals {}; + library::short_string Txt, SyText, ErrMsg, FileId; + int64_t XCount, Size; + + FileName = FileList->FileName( FNr ); + FileId = FileList->FileId( FNr ); + + std::cout << "Reading file: " << FileName << std::endl; + gdxCreate( &PGX, ErrMsg.data(), ErrMsg.length() ); + gdxOpenRead( PGX, FileName.data(), &ErrNr ); + if( ErrNr != 0 ) + { + gdxErrorStr( nullptr, ErrNr, ErrMsg.data() ); + library::printErrorMessage( "\nError reading file, message: " + ErrMsg.string() ); + return; + } + InputFilesRead++; + + ShareAcronyms( PGX ); + + gdxUELRegisterStrStart( PGXMerge ); + gdxSystemInfo( PGX, &NrSy, &NrUel ); + FrstError = true; + + for( N = 1; N <= NrSy; N++ ) + { + gdxSymbolInfo( PGX, N, SyName.data(), &Dim, &SyITyp ); + gdxSymbolInfoX( PGX, N, &DummyCount, &SySubTyp, SyText.data() ); + if( CheckError( Dim < GMS_MAX_INDEX_DIM, "Dimension too large" ) ) + continue; + + SyTyp = gdxSyType( SyITyp ); + if( SyITyp == GMS_DT_ALIAS ) + { + // We cannot create two dimensional aliased sets! + SyTyp = dt_set; + SySubTyp = 0; + } + + SyIndx = gdlib::gmsobj::TXHashedStringList::IndexOf( SyName.data() ); + if( SyIndx < 0 ) + { + SyIndx = AddSymbol( SyName.data(), Dim + 1, SyTyp, SySubTyp ); + if( SyIndx < 0 ) + continue; + } + + SyObj = gdlib::gmsobj::TXHashedStringList::GetObject( SyIndx ); + if( SyObj->SyData == nullptr ) + continue; + if( SyObj->SySkip ) + continue; + + // 64 bit + XCount = static_cast( DummyCount ); + Size = XCount * SyObj->SyDim; + if( SyTyp == dt_var || SyTyp == dt_equ ) + RecLen = 4; + else + RecLen = 1; + + Size = Size * RecLen; + + if( Pass == TProcessPass::RpScan || Pass == TProcessPass::RpDoAll ) + { + SyObj->SySize += Size; + SyObj->SyMemory += XCount * ( SyObj->SyDim * sizeof( int ) + RecLen * sizeof( double ) ); + if( CheckError( SyObj->SyData->GetCount() + XCount <= std::numeric_limits::max(), "Element count for symbol > maxint" ) ) + { + SyObj->SySkip = true; + SyObj->SyData.reset(); + continue; + } +#if defined( OLD_MEMORY_CHECK ) + if( CheckError( SyObj->SyMemory <= std::numeric_limits::max(), "Symbol is too large" ) ) + { + SyObj->SySkip = true; + SyObj->SyData.reset(); + continue; + } +#endif + } + + if( Pass == TProcessPass::RpScan ) + continue; + + if( Pass == TProcessPass::RpSmall && SyObj->SySize >= SizeCutOff ) + continue; + if( Pass == TProcessPass::RpBig && SyObj->SySize < SizeCutOff ) + continue; + + if( CheckError( Dim + 1 == SyObj->SyDim, "Dimensions do not match" ) ) + continue; + if( CheckError( SyTyp == SyObj->SyTyp, "Types do not match" ) ) + continue; + if( ( SyTyp == dt_var || SyTyp == dt_equ ) && CheckError( SySubTyp == SyObj->SySubTyp, "Var/Equ subtypes do not match" ) ) + continue; + + if( SyObj->SyExplTxt.empty() ) + SyObj->SyExplTxt = SyText; + else if( !SyText.empty() ) + CheckError( SyObj->SyExplTxt == SyText, "Explanatory text is different" ); + + IndxI[0] = AddUEL( FileId ); + gdxDataReadStrStart( PGX, N, &NrRecs ); + while( gdxDataReadStr( PGX, IndxSPtrs, Vals, &FDim ) != 0 ) + { + if( Dim > 0 ) + for( D = FDim - 1; D < Dim; D++ ) + IndxI[D + 1] = AddUEL( library::short_string { IndxSPtrs[D] } ); + if( SyTyp == dt_set && Vals[GMS_VAL_LEVEL] != 0 ) + { + gdxGetElemText( PGX, static_cast( std::round( Vals[GMS_VAL_LEVEL] ) ), Txt.data(), &INode ); + Vals[GMS_VAL_LEVEL] = StrPool->Add( Txt.data(), Txt.length() ); + } + SyObj->SyData->AddRecord( IndxI, Vals ); + } + gdxDataReadDone( PGX ); + } + + KeepNewAcronyms( PGX ); + gdxClose( PGX ); + gdxFree( &PGX ); + + gdxUELRegisterDone( PGXMerge ); +} + +bool TSymbolList::CollectBigOne( const int SyNr ) +{ + gdxHandle_t PGX; + int N, NrRecs, FDim, D, INode, ErrNr, FNr; + TGAMSSymbol *SyObj; + gdxStrIndex_t IndxS {}; + gdxStrIndexPtrs_t IndxSPtrs; + GDXSTRINDEXPTRS_INIT( IndxS, IndxSPtrs ); + gdxUelIndex_t IndxI {}; + gdxValues_t Vals {}; + library::short_string Txt, ErrMsg, FileName, FileId; + + SyObj = gdlib::gmsobj::TXHashedStringList::GetObject( SyNr ); + if( SyObj->SyData == nullptr ) + return false; + + // Replaced '\r' with '\n' here: + std::cout << "\nlooking for symbol " + << gdlib::gmsobj::TXHashedStringList::GetString( SyNr ) + << " "; + + gdxUELRegisterStrStart( PGXMerge ); + + for( FNr = 0; FNr < FileList->size(); FNr++ ) + { + FileName = FileList->FileName( FNr ); + FileId = FileList->FileId( FNr ); + gdxCreate( &PGX, ErrMsg.data(), ErrMsg.length() ); + gdxOpenRead( PGX, FileName.data(), &ErrNr ); + if( ErrNr != 0 ) + { + gdxErrorStr( nullptr, ErrNr, ErrMsg.data() ); + library::printErrorMessage( "Error reading file, message: " + ErrMsg.string() ); + return false; + } + + if( gdxFindSymbol( PGX, gdlib::gmsobj::TXHashedStringList::GetString( SyNr ), &N ) > 0 ) + { + // We did this already in AddPGXFile: + // ShareAcronyms(PGX); + IndxI[0] = AddUEL( FileId ); + gdxDataReadStrStart( PGX, N, &NrRecs ); + while( gdxDataReadStr( PGX, IndxSPtrs, Vals, &FDim ) != 0 ) + { + for( D = FDim - 1; D <= SyObj->SyDim; D++ ) + IndxI[D + 1] = AddUEL( library::short_string { IndxSPtrs[D] } ); + if( SyObj->SyTyp == dt_set && Vals[GMS_VAL_LEVEL] != 0 ) + { + gdxGetElemText( PGX, static_cast( std::round( Vals[GMS_VAL_LEVEL] ) ), Txt.data(), &INode ); + Vals[GMS_VAL_LEVEL] = StrPool->Add( Txt.data(), Txt.length() ); + } + SyObj->SyData->AddRecord( IndxI, Vals ); + } + gdxDataReadDone( PGX ); + } + + KeepNewAcronyms( PGX ); + gdxClose( PGX ); + gdxFree( &PGX ); + } + + gdxUELRegisterDone( PGXMerge ); + return true; +} + +bool TSymbolList::FindGDXFiles( const std::string &Path ) +{ + rtl::sysutils_p3::TSearchRec Rec {}; + std::string WPath, BPath, ShortName, NewName, DTS; + double DT; + + // Normal file or file pattern + bool Result { true }; + + WPath = Path; + BPath = gdlib::strutilx::ExtractFilePathEx( WPath ); + if( FindFirst( WPath, rtl::sysutils_p3::faAnyFile, Rec ) == 0 ) + { + do { + if( Rec.Name == "." || Rec.Name == ".." ) + continue; + if( OutFile == BPath + Rec.Name ) + { + std::cout << "Cannot use " << OutFile << " as input file and output file, skipped it as input" << std::endl; + Result = false; + continue; + } + + ShortName = gdlib::strutilx::ChangeFileExtEx( Rec.Name, "" ); + if( !library::goodUELString( ShortName.data(), ShortName.length() ) || utils::trim( ShortName ).empty() ) + { + NewName = "File_" + std::to_string( FileList->size() + 1 ); + std::cout << "*** Filename cannot be used as a valid UEL\n" + << " Existing name: " << ShortName << '\n' + << " Replaced with: " << NewName << std::endl; + ShortName = NewName; + } + + DT = rtl::sysutils_p3::FileDateToDateTime( Rec.Time ); + + uint16_t Year, Month, Day, Hour, Min, Sec, MSec; + rtl::sysutils_p3::DecodeDate( DT, Year, Month, Day ); + rtl::sysutils_p3::DecodeTime( DT, Hour, Min, Sec, MSec ); + + DTS = FormatDateTime( Year, Month, Day, Hour, Min, Sec ); + FileList->AddFile( BPath + Rec.Name, ShortName, DTS + " " + BPath + Rec.Name ); + } while( FindNext( Rec ) == 0 ); + FindClose( Rec ); + } + else + { + std::cout << '"' << Path << "\" is no valid pattern for an existing input file name, skipped it as input" << std::endl; + Result = false; + } + + return Result; +} + +void TSymbolList::WritePGXFile( const int SyNr, const TProcessPass Pass ) +{ + TGAMSSymbol *SyObj; + int R, INode; + gdxUelIndex_t IndxI {}; + gdxValues_t Vals {}; + library::short_string Txt; + + SyObj = gdlib::gmsobj::TXHashedStringList::GetObject( SyNr ); + if( SyObj->SyData == nullptr ) + return; + if( Pass == TProcessPass::RpSmall && SyObj->SySize >= SizeCutOff ) + return; + + SyObj->SyData->Sort(); + gdxDataWriteRawStart( PGXMerge, gdlib::gmsobj::TXHashedStringList::GetString( SyNr ), SyObj->SyExplTxt.data(), SyObj->SyDim, static_cast( SyObj->SyTyp ), SyObj->SySubTyp ); + for( R = 0; R < SyObj->SyData->GetCount(); R++ ) + { + SyObj->SyData->GetRecord( R, IndxI, Vals ); + if( SyObj->SyTyp == dt_set && Vals[GMS_VAL_LEVEL] != 0 ) + { + Txt = StrPool->GetString( static_cast( std::round( Vals[GMS_VAL_LEVEL] ) ) ); + gdxAddSetText( PGXMerge, Txt.data(), &INode ); + Vals[GMS_VAL_LEVEL] = INode; + } + gdxDataWriteRaw( PGXMerge, IndxI, Vals ); + } + gdxDataWriteDone( PGXMerge ); + SyObj->SyData->Clear(); + SyObj->SyData.reset(); +} + +void TSymbolList::WriteNameList() +{ + const std::string BASE_NAME { "Merged_set_" }; + library::short_string SetName; + int N, SyNr, TextNr; + gdxStrIndex_t AIndex {}; + gdxStrIndexPtrs_t AIndexPtrs; + GDXSTRINDEXPTRS_INIT( AIndex, AIndexPtrs ); + gdxValues_t AVals {}; + + // Find unique name for the merged set + N = 1; + do { + SetName = BASE_NAME + std::to_string( N ); + gdxFindSymbol( PGXMerge, SetName.data(), &SyNr ); + if( SyNr < 0 ) + break; + N++; + } while( true ); + + gdxDataWriteStrStart( PGXMerge, SetName.data(), "Merge set", 1, 0, 0 ); + for( N = 0; N < FileList->size(); N++ ) + { + gdxAddSetText( PGXMerge, FileList->FileInfo( N ).data(), &TextNr ); + AVals[GMS_VAL_LEVEL] = TextNr; + strcpy( AIndexPtrs[0], FileList->FileId( N ).data() ); + gdxDataWriteStr( PGXMerge, const_cast( AIndexPtrs ), AVals ); + } + gdxDataWriteDone( PGXMerge ); +} + +void TSymbolList::KeepNewAcronyms( const gdxHandle_t &PGX ) +{ + int NN { gdxAcronymNextNr( PGX, -1 ) }; + if( NN <= NextAcroNr ) + return; + + int OrgIndx, NewIndx, AutoIndx, AIndx; + library::short_string AName, AText; + for( int N { 1 }; N <= gdxAcronymCount( PGX ); N++ ) + { + gdxAcronymGetMapping( PGX, N, &OrgIndx, &NewIndx, &AutoIndx ); + if( NewIndx >= NextAcroNr ) + { + gdxAcronymGetInfo( PGX, N, AName.data(), AText.data(), &AIndx ); + if( AName.empty() ) + { + // Should not happen + for( int K { 1 }; K <= std::numeric_limits::max(); K++ ) + { + AName = "Acronym_Auto_" + std::to_string( K ); + if( FindAcronym( AName ) < 0 ) + break; + } + AText = "GDX file did not have a name for acronym"; + } + gdxAcronymAdd( PGXMerge, AName.data(), AText.data(), NewIndx ); + } + } + + NextAcroNr = NN; +} + +void TSymbolList::ShareAcronyms( const gdxHandle_t &PGX ) +{ + if( gdxAcronymCount( PGX ) == 0 ) + { + gdxAcronymNextNr( PGX, 0 ); + return; + } + + library::short_string AName, AText; + int AIndx; + if( NextAcroNr == 0 ) + { + gdxAcronymGetInfo( PGX, 1, AName.data(), AText.data(), &AIndx ); + NextAcroNr = AIndx; + } + + gdxAcronymNextNr( PGX, NextAcroNr ); + + library::short_string ANameM, ATextM; + int NM, AIndxM; + for( int N { 1 }; N <= gdxAcronymCount( PGX ); N++ ) + { + gdxAcronymGetInfo( PGX, N, AName.data(), AText.data(), &AIndx ); + NM = FindAcronym( AName ); + if( NM <= 0 ) + continue; + gdxAcronymGetInfo( PGXMerge, NM, ANameM.data(), ATextM.data(), &AIndxM ); + library::assertWithMessage( AIndxM > 0, "ShareAcronyms-1" ); + gdxAcronymSetInfo( PGX, N, AName.data(), AText.data(), AIndxM ); + } +} + +int TSymbolList::FindAcronym( const library::short_string &Id ) +{ + library::short_string AName, AText; + int AIndx; + for( int N { 1 }; N <= gdxAcronymCount( PGXMerge ); N++ ) + { + gdxAcronymGetInfo( PGXMerge, N, AName.data(), AText.data(), &AIndx ); + if( gdlib::strutilx::StrUEqual( Id.string(), AName.string() ) ) + return N; + } + return {}; +} + +int TSymbolList::GetFErrorCount() const +{ + return FErrorCount; +} + +int TSymbolList::GetFileListSize() const +{ + return FileList->size(); +} + +bool TSymbolList::IsIncludeListEmpty() const +{ + return IncludeList.empty(); +} + +bool TSymbolList::IsExcludeListEmpty() const +{ + return ExcludeList.empty(); +} + +void TSymbolList::AddToIncludeList( const std::string &item ) +{ + IncludeList.emplace_back( item ); +} + +void TSymbolList::AddToExcludeList( const std::string &item ) +{ + ExcludeList.emplace_back( item ); +} + +std::string FormatDateTime( const uint16_t Year, const uint16_t Month, const uint16_t Day, + const uint16_t Hour, const uint16_t Min, const uint16_t Sec ) +{ + auto Int2 = []( const int n ) -> std::string { + std::ostringstream oss; + oss << std::setw( 2 ) << std::setfill( '0' ) << n; + return oss.str(); + }; + + return Int2( Year ) + '/' + Int2( Month ) + '/' + Int2( Day ) + ' ' + + Int2( Hour ) + ':' + Int2( Min ) + ':' + Int2( Sec ); +} + +bool GetParameters( const int argc, const char *argv[] ) +{ + enum class KP : uint8_t + { + Id = 1, + Exclude, + Big, + Strict, + Output + }; + + std::unique_ptr CmdParams; + int ParNr, KW, X, K; + std::string KS, Id; + + // TODO: Merge declarations and initializations? + DoBigSymbols = false; + SizeCutOff = 10000000; + // Probably unnecessary: + OutFile.clear(); + StrictMode = false; + CmdParams = std::make_unique(); + + CmdParams->AddParam( static_cast( KP::Id ), "ID" ); + CmdParams->AddParam( static_cast( KP::Exclude ), "EXCLUDE" ); + CmdParams->AddParam( static_cast( KP::Big ), "BIG" ); + CmdParams->AddParam( static_cast( KP::Strict ), "STRICT" ); + // TODO: Output also in capital letters? + CmdParams->AddParam( static_cast( KP::Output ), "Output" ); + CmdParams->AddParam( static_cast( KP::Output ), "O" ); + + bool Result { CmdParams->CrackCommandLine( argc, argv ) }; + if( Result ) + { + ParNr = 0; + do { + library::cmdpar::TParamRec ParamRec { CmdParams->GetParams( ParNr ) }; + KW = ParamRec.Key; + KS = ParamRec.KeyS; + + ParNr++; + + switch( static_cast( KW ) ) + { + case KP::Id: + case KP::Exclude: + while( !KS.empty() ) + { + K = gdlib::strutilx::LChSetPos( + ", ", + KS.data(), + static_cast( KS.length() ) ); + if( K == -1 ) + { + Id = KS; + KS.clear(); + } + else + { + Id = KS.substr( 0, K ); + KS.erase( 0, K + 1 ); + KS = utils::trim( KS ); + } + Id = utils::trim( Id ); + if( !Id.empty() ) + { + if( KW == static_cast( KP::Id ) ) + { + std::cout << "Include Id: " << Id << std::endl; + SyList->AddToIncludeList( utils::trim( Id ) ); + } + else + { + std::cout << "Exclude Id: " << Id << std::endl; + SyList->AddToExcludeList( utils::trim( Id ) ); + } + } + } + break; + + case KP::Big: + DoBigSymbols = true; + if( !KS.empty() ) + { + utils::val( KS, X, K ); + if( K == 0 ) + SizeCutOff = X; + } + break; + + case KP::Strict: + if( gdlib::strutilx::StrUEqual( KS, "true" ) ) + StrictMode = true; + break; + + case KP::Output: + if( OutFile.empty() ) + OutFile = KS; + else + { + library::printErrorMessage( "*** Error: Only one output file can be specified" ); + Result = false; + } + break; + + default: + FilePatterns.emplace_back( KS ); + // SyList->FindGDXFiles( KS ); + break; + } + } while( ParNr < CmdParams->GetParamCount() ); + } + + if( OutFile.empty() ) + OutFile = "merged.gdx"; + else if( gdlib::strutilx::ExtractFileExtEx( OutFile.string() ).empty() ) + OutFile = gdlib::strutilx::ChangeFileExtEx( OutFile.string(), ".gdx" ); + + return Result; +} + +void Usage( const library::AuditLine &AuditLine ) +{ + std::cout << "gdxmerge: Merge GDX files\n" + << AuditLine.getAuditLine() << "\n\n" + << "Usage:\n" + << " gdxmerge filepat1 filepat2 ... filepatn\n" + << " Optional parameters:\n" + << " id=ident1, ident2: Merge specified IDs only\n" + << " exclude=ident1, ident2: Do not merge specified IDs\n" + << " big= : Size indicator for a large symbol\n" + << " output=filename : Output file; merged.gdx by default\n" + << " strict=true/false: Terminate on failure, e.g. missing input files\n" + << "Filepat represents a filename or a file pattern.\n" + << "The form: @filename will process parameters from that file" << std::endl; +} + +int main( const int argc, const char *argv[] ) +{ + library::short_string Msg; + int N, ErrNr; + + library::AuditLine AuditLine { "GDXMERGE" }; + if( argc > 1 && gdlib::strutilx::StrUEqual( argv[1], "AUDIT" ) ) + { + std::cout << AuditLine.getAuditLine() << std::endl; + return {}; + } + + if( argc == 1 || ( argc == 2 && std::strcmp( argv[1], "/?" ) == 0 ) ) + { + Usage( AuditLine ); + return {}; + } + + if( !gdxGetReady( Msg.data(), Msg.length() ) ) + { + library::printErrorMessage( "*** Error: Unable to load gdx library, message:\n" + Msg.string() ); + return 1; + } + + SyList = std::make_unique(); + + if( !GetParameters( argc, argv ) ) + { + library::printErrorMessage( "*** Error: Parameter error" ); + return 1; + } + + if( rtl::sysutils_p3::FileExists( OutFile.string() ) ) + { + if( StrictMode ) + { + library::printErrorMessage( "*** Error : Output file \"" + OutFile.string() + "\" already exists (strict mode)" ); + return 1; + } + else + rtl::sysutils_p3::DeleteFileFromDisk( OutFile.string() ); + } + + if( !SyList->IsIncludeListEmpty() && !SyList->IsExcludeListEmpty() ) + { + // TODO: Use four stars here instead of the usual three? + library::printErrorMessage( "**** The options \"ID\" and \"Exclude\" are mutual exclusive" ); + return 1; + } + + SyList->OpenOutput( OutFile, ErrNr ); + std::cout << "Output file: " << OutFile << std::endl; + if( ErrNr != 0 ) + { + library::printErrorMessage( "*** Error : Cannot write to output file, Error Nr = " + std::to_string( ErrNr ) ); + gdxErrorStr( nullptr, ErrNr, Msg.data() ); + library::printErrorMessage( "*** Message: " + Msg.string() ); + return 1; + } + + for( const std::string &FilePattern: FilePatterns ) + if( !SyList->FindGDXFiles( FilePattern ) && StrictMode ) + { + library::printErrorMessage( "*** Error : Issue with file name \"" + FilePattern + "\" (strict mode)" ); + return 1; + } + InputFilesRead = 0; + if( !DoBigSymbols ) + { + for( N = 0; N < SyList->GetFileListSize(); N++ ) + SyList->AddPGXFile( N, TProcessPass::RpDoAll ); + + for( N = 0; N < SyList->size(); N++ ) + SyList->WritePGXFile( N, TProcessPass::RpDoAll ); + } + else + { + for( N = 0; N < SyList->GetFileListSize(); N++ ) + SyList->AddPGXFile( N, TProcessPass::RpScan ); + + for( N = 0; N < SyList->GetFileListSize(); N++ ) + SyList->AddPGXFile( N, TProcessPass::RpSmall ); + + for( N = 0; N < SyList->size(); N++ ) + SyList->WritePGXFile( N, TProcessPass::RpSmall ); + + for( N = 0; N < SyList->size(); N++ ) + if( SyList->CollectBigOne( N ) ) + SyList->WritePGXFile( N, TProcessPass::RpBig ); + } + + SyList->WriteNameList(); + + gdxClose( PGXMerge ); + gdxFree( &PGXMerge ); + + std::cout << std::endl; + + if( SyList->GetFErrorCount() > 0 ) + std::cout << "Number of errors reported = " << SyList->GetFErrorCount() << std::endl; + + SyList->Clear(); + // UnloadGDXLibrary(); + + if( InputFilesRead == 0 ) + { + if( StrictMode ) + { + library::printErrorMessage( "*** Error : No valid input files specified (strict mode)" ); + return 1; + } + else + std::cout << "No valid input files specified" << std::endl; + } + else + std::cout << "Merge complete, " << InputFilesRead << " input files merged" << std::endl; + + return {}; +} + +}// namespace gdxmerge + +int main( const int argc, const char *argv[] ) +{ + return gdxmerge::main( argc, argv ); +} diff --git a/src/tools/gdxmerge/gdxmerge.h b/src/tools/gdxmerge/gdxmerge.h new file mode 100644 index 0000000..da85647 --- /dev/null +++ b/src/tools/gdxmerge/gdxmerge.h @@ -0,0 +1,106 @@ +#ifndef GDX_GDXMERGE_H +#define GDX_GDXMERGE_H + +#include +#include +#include +#include +#include + +#include "../library/common.h" +#include "../library/short_string.h" +#include "../../gdlib/gmsdata.h" +#include "../../gdlib/gmsobj.h" + +// GDX library interface +#include "../../../generated/gdxcc.h" +// Global constants +#include "../../../generated/gclgms.h" + +namespace gdxmerge +{ + +constexpr std::array DataTypSize { 1, 1, 5, 5, 0 }; + +enum class TProcessPass : uint8_t +{ + RpDoAll, + RpScan, + RpSmall, + RpBig, + RpTooBig +}; + +struct TGAMSSymbol { + int SyDim, SySubTyp; + gdxSyType SyTyp; + std::unique_ptr> SyData; + library::short_string SyExplTxt; + int64_t SySize {}, SyMemory {}; + bool SySkip {}; + + TGAMSSymbol( int ADim, gdxSyType AType, int ASubTyp ); + + // void SetCurrentFile( const std::string &S ); +}; + +struct TGDXFileEntry { + std::string FFileName, FFileId, FFileInfo; + + TGDXFileEntry( const std::string &AFileName, const std::string &AFileId, const std::string &AFileInfo ); +}; + +template +struct TFileList final : public gdlib::gmsobj::TXList { + ~TFileList() override; + void Clear() override; + void AddFile( const std::string &AFileName, const std::string &AFileId, const std::string &AFileInfo ); + std::string FileName( int Index ); + std::string FileId( int Index ); + std::string FileInfo( int Index ); +}; + +class TSymbolList : public gdlib::gmsobj::TXHashedStringList +{ + std::unique_ptr> StrPool; + int FErrorCount {}, NextAcroNr {}; + std::unique_ptr> FileList; + std::vector IncludeList, ExcludeList; + +public: + TSymbolList(); + ~TSymbolList() override; + void Clear() override; + + static void OpenOutput( const library::short_string &AFileName, int &ErrNr ); + static int AddUEL( const library::short_string &S ); + int AddSymbol( const std::string &AName, int ADim, gdxSyType AType, int ASubTyp ); + void AddPGXFile( int FNr, TProcessPass Pass ); + bool CollectBigOne( int SyNr ); + bool FindGDXFiles( const std::string &Path ); + void WritePGXFile( int SyNr, TProcessPass Pass ); + void WriteNameList(); + void KeepNewAcronyms( const gdxHandle_t &PGX ); + void ShareAcronyms( const gdxHandle_t &PGX ); + int FindAcronym( const library::short_string &Id ); + + int GetFErrorCount() const; + int GetFileListSize() const; + bool IsIncludeListEmpty() const; + bool IsExcludeListEmpty() const; + void AddToIncludeList( const std::string &item ); + void AddToExcludeList( const std::string &item ); +}; + +std::string FormatDateTime( uint16_t Year, uint16_t Month, uint16_t Day, + uint16_t Hour, uint16_t Min, uint16_t Sec ); + +bool GetParameters( int argc, const char *argv[] ); + +void Usage( const library::AuditLine &AuditLine ); + +int main( int argc, const char *argv[] ); + +}// namespace gdxmerge + +#endif//GDX_GDXMERGE_H diff --git a/src/tools/library/cmdpar.cpp b/src/tools/library/cmdpar.cpp new file mode 100644 index 0000000..8ca70b6 --- /dev/null +++ b/src/tools/library/cmdpar.cpp @@ -0,0 +1,358 @@ +#include +// #include + +#include "cmdpar.h" +#include "common.h" +#include "../../gdlib/utils.h" +#include "../../gdlib/strutilx.h" + +namespace library::cmdpar +{ + +void TCmdParams::ClearParams() +{ + FKeyList.clear(); + FParList.clear(); +} + +void TCmdParams::AddVS( int v, const std::string &s ) +{ + for( const auto &pair: FKeyList ) + if( gdlib::strutilx::UpperCase( pair.first ) == gdlib::strutilx::UpperCase( s ) ) + library::assertWithMessage( false, "Duplicate keyword!" ); + FKeyList.emplace_back( s, v ); +} + +// Search from the end so the LAST value will be used +int TCmdParams::FindKeyV( int V ) +{ + for( int N { GetParamCount() - 1 }; N >= 0; N-- ) + if( GetParams( N ).Key == V ) + return N; + return -1; +} + +// Brief: +// Crack the command line +// Returns: +// True if there were no synyax errors; false otherwise +// Description: +// After specifying the strings and integer values for +// keywords and parameters (see AddKeyW and AddParam) +// this function will build the list of keywords and parameters. +// A string that starts with '@' is interpreted as the name of +// a file with additional parameters. +bool TCmdParams::CrackCommandLine( const int ParamCount, const char *ParamStr[] ) +{ + return AddParameters( 0, {}, ParamCount, ParamStr ); +} + +// Brief: +// Insert additional parameters +// Parameters: +// AInsp: Insertion point for additional parameters +// CmdLine: Additional parameters +// Description: +// This function is used to add more parameters to the parameter list +// that could not be processed while reading the command line for the +// first time. +bool TCmdParams::AddParameters( const int AInsP, const std::string &CmdLine, const int ParamCount, const char *ParamStr[] ) +{ + std::string FParams; + int xr, maxr; + + auto SkipBl = [&]() { + while( xr <= maxr && FParams.at( xr ) <= ' ' ) + xr++; + }; + + auto NextToken = [&]() -> std::string { + std::string result; + SkipBl(); + if( xr <= maxr ) + { + std::vector StopSet; + if( FParams.at( xr ) != '"' ) + StopSet = { ' ', '\t' }; + else + { + StopSet = { '"' }; + xr++; + } + StopSet.emplace_back( '=' ); + int xk {}; + for( int k { 1 }; k <= maxr - xr + 1; k++ ) + if( utils::in( FParams.at( xr + k - 1 ), StopSet ) ) + { + xk = k; + break; + } + if( xk == 0 ) + { + result.append( FParams.substr( xr, FParams.length() - xr ) ); + xr = maxr + 1; + } + else + { + result.append( FParams.substr( xr, xk - 1 ) ); + xr += xk; + } + } + // std::cout << "token = |" << result << '|' << std::endl; + return result; + }; + + auto NextKey = [&]( std::string &s ) -> int { + s.clear(); + std::string T { NextToken() }; + if( T.empty() ) + return static_cast( CmdParamStatus::ke_empty ); + else + { + int result { -1 }; + for( const auto &pair: FKeyList ) + if( gdlib::strutilx::UpperCase( pair.first ) == gdlib::strutilx::UpperCase( T ) ) + { + result = pair.second; + break; + } + if( result < 0 ) + { + if( T.front() == '@' ) + { + s = T.substr( 1 ); + return static_cast( CmdParamStatus::ke_badfile ); + } + else + { + s = T; + return static_cast( CmdParamStatus::ke_unknown ); + } + } + else if( result >= static_cast( CmdParamStatus::kk_big ) ) + { + result -= static_cast( CmdParamStatus::kk_big ); + SkipBl(); + if( xr <= maxr && FParams.at( xr ) == '=' ) + xr++; + s = NextToken(); + if( s.empty() ) + return static_cast( CmdParamStatus::ke_noparam ); + else + { + int k { static_cast( s.length() ) }; + if( k >= 2 && s.front() == '\'' && s.back() == '\'' ) + s = s.substr( 1, k - 2 ); + } + } + return result; + } + }; + + auto ExpandCommandLine = [&]() -> std::string { + std::string result; + for( int N { 1 }; N < ParamCount; N++ ) + { + std::string S { ParamStr[N] }; + if( S.empty() ) + continue; + if( S.find( ' ' ) == std::string::npos ) + result.append( ' ' + S ); + else + { + size_t k { S.find( '=' ) }; + if( k == std::string::npos ) + { + if( S.front() != '@' ) + result.append( " \"" + S + '"' ); + else + // Keep double-quote after @ sign + result.append( " @\"" + S.substr( 1 ) + '"' ); + } + else + { + result.append( ' ' + S.substr( 0, k + 1 ) ); + result.append( " \"" + S.substr( k + 1 ) + '"' ); + } + } + } + return result; + }; + + auto ExpandFiles = []( const std::string &Src, std::string &Dest ) -> bool { + constexpr int MAXBUF { 4 }; + bool result { true }; + Dest.clear(); + int sp {}; + do { + int fnd { -1 }; + for( int k { sp }; k < static_cast( Src.length() ); k++ ) + if( Src[k] == '@' ) + { + fnd = k; + break; + } + if( fnd == -1 ) + { + Dest.append( Src.substr( sp, Src.length() - sp + 2 ) ); + break; + } + Dest.append( Src.substr( sp, fnd - sp ) ); + sp = fnd + 1; + if( sp >= static_cast( Src.length() ) ) + { + result = false; + break; + } + char Brk; + if( Src.at( sp ) != '"' ) + Brk = ' '; + else + { + sp++; + Brk = '"'; + } + + std::string fname; + { + int k { sp }; + while( k >= 0 && k < static_cast( Src.length() ) && Src[k] != Brk ) + k++; + if( k > static_cast( Src.length() ) ) + { + result = false; + break; + } + fname = Src.substr( sp, k - sp ); + sp = k; + } + + if( sp >= 0 && sp < static_cast( Src.length() ) && Src[sp] == '"' ) + sp++; + if( gdlib::strutilx::ExtractFileExtEx( fname ).empty() ) + fname = gdlib::strutilx::ChangeFileExtEx( fname, ".txt" ); + std::ifstream fi( fname ); + if( !fi.is_open() ) + { + library::printErrorMessage( "**** could not open file: " + fname ); + result = false; + } + else + { + std::string line; + while( getline( fi, line ) ) + { + std::vector strings = library::splitString( line, ' ' ); + if( strings.front().empty() || strings.front().front() == '*' ) + continue; + for( size_t k {}; k < strings.size(); k++ ) + { + if( strings[k].empty() ) + break; + Dest.append( " " + strings[k] ); + } + // TODO: Check whether this if is still necessary + if( strings.back().length() == 255 ) + { + library::printErrorMessage( "**** Input line longer than " + std::to_string( MAXBUF * 255 ) + " characters" ); + result = false; + } + } + fi.close(); + } + } while( true ); + // Dest.Write; + return result; + }; + + /* AddParameters */ + + int Insp; + bool result; + if( !CmdLine.empty() ) + { + Insp = AInsP; + result = ExpandFiles( CmdLine, FParams ); + } + else + { + Insp = 0; + const std::string wrk { ExpandCommandLine() }; + result = ExpandFiles( wrk, FParams ); + } + + xr = 0; + maxr = static_cast( FParams.length() ) - 1; + do { + std::string ks; + const int kw = NextKey( ks ); + if( kw == static_cast( CmdParamStatus::ke_empty ) ) + break; + FParList.insert( { Insp, TParamRec { kw, ks } } ); + Insp++; + } while( true ); + + if( !HasKey( static_cast( CmdParamStatus::kp_input ) ) && GetParams( 0 ).Key == static_cast( CmdParamStatus::ke_unknown ) ) + FParList.at( 0 ).Key = static_cast( CmdParamStatus::kp_input ); + + return result; +} + +void TCmdParams::AddKeyWord( int v, const std::string &s ) +{ + library::assertWithMessage( v >= 0 && v < static_cast( CmdParamStatus::kk_big ), + "Bad value for AddKeyw = " + std::to_string( v ) ); + AddVS( v, utils::trim( s ) ); +} + +void TCmdParams::AddParam( int v, const std::string &s ) +{ + library::assertWithMessage( v >= 0 && v < static_cast( CmdParamStatus::kk_big ), + "Bad value for AddKeyw = " + std::to_string( v ) ); + AddVS( v + static_cast( CmdParamStatus::kk_big ), utils::trim( s ) ); +} + +bool TCmdParams::HasParam( int v, library::short_string &s ) +{ + int N = FindKeyV( v ); + bool result = N >= 0; + if( !result ) + s.clear(); + else + s = GetParams( N ).KeyS; + return result; +} + +// Brief: +// Check if a keyword was specified +// Parameters: +// V: Key number +// Returns: +// True if keyword was specified; false otherwise +bool TCmdParams::HasKey( int v ) +{ + return FindKeyV( v ) >= 0; +} + +TParamRec TCmdParams::GetParams( int n ) +{ + if( n >= 0 && n < static_cast( FParList.size() ) ) + return FParList[n]; + else + return TParamRec { static_cast( CmdParamStatus::ke_empty ), {} }; +} + +int TCmdParams::GetParamCount() const +{ + return static_cast( FParList.size() ); +} + +std::string TCmdParams::GetParamText( int key ) const +{ + for( const auto &pair: FKeyList ) + if( pair.second == key ) + return pair.first; + return "?" + std::to_string( key ) + "?"; +} + +}// namespace library::cmdpar diff --git a/src/tools/library/cmdpar.h b/src/tools/library/cmdpar.h new file mode 100644 index 0000000..62385b2 --- /dev/null +++ b/src/tools/library/cmdpar.h @@ -0,0 +1,59 @@ +#ifndef GDX_CMDPAR_H +#define GDX_CMDPAR_H + +#include +#include +#include +#include + +#include "../library/short_string.h" + +// Description: +// Object that handles command line parameters + +namespace library::cmdpar +{ + +// Parameter record +struct TParamRec { + int Key {}; // key number + std::string KeyS;// key value +}; +using PParamRec = TParamRec *; + +// Class to handle command line parameters +class TCmdParams +{ + std::vector> FKeyList; + std::map FParList; + + void ClearParams(); + void AddVS( int v, const std::string &s ); + int FindKeyV( int V ); + +public: + bool CrackCommandLine( int ParamCount, const char *ParamStr[] ); + bool AddParameters( int AInsP, const std::string &CmdLine, int ParamCount, const char *ParamStr[] ); + void AddKeyWord( int v, const std::string &s ); + void AddParam( int v, const std::string &s ); + bool HasParam( int v, library::short_string &s ); + bool HasKey( int v ); + + TParamRec GetParams( int n ); + [[nodiscard]] int GetParamCount() const; + [[nodiscard]] std::string GetParamText( int key ) const; +}; + +enum class CmdParamStatus : int16_t +{ + kp_input, + ke_empty = -1, + ke_unknown = -2, + ke_noparam = -3, + ke_badfile = -4, + kk_big = 10000 +}; + +}// namespace library::cmdpar + +#endif//GDX_CMDPAR_H diff --git a/src/tools/library/common.cpp b/src/tools/library/common.cpp new file mode 100644 index 0000000..b7224f7 --- /dev/null +++ b/src/tools/library/common.cpp @@ -0,0 +1,225 @@ +#include +#include +#include +#include +#include +#include + +#include "common.h" +// Global constants +#include "../../../generated/gclgms.h" + +namespace library +{ + +std::ostream &errorStream { std::cout }; +// TODO: Possible improvement for later, but currently results in problems with the tests +// std::ostream &errorStream { std::cerr }; + +void printErrorMessage( const std::string &message, const bool printError ) +{ + if( printError ) errorStream << "Error: "; + errorStream << message << std::endl; +} + +void assertWithMessage( const bool expression, const std::string &message ) +{ + if( !expression ) printErrorMessage( message ); + assert( expression ); +} + +std::string gdxSpecialValuesStr( const int i ) +{ + if( i < 0 || i >= GMS_SVIDX_MAX ) + { + assertWithMessage( false, "Unknown type" ); + return "Unknown"; + } + else + return gmsSVText[i]; +} + +std::string gdxDataTypStr( const int i ) +{ + switch( i ) + { + case 0: + return "Set"; + case 1: + return "Par"; + case 2: + return "Var"; + case 3: + return "Equ"; + case 4: + return "Alias"; + default: + assertWithMessage( false, "Unknown type" ); + return "Unknown"; + } +} + +std::string gdxDataTypStrL( const int i ) +{ + if( i < 0 || i >= GMS_DT_MAX ) + { + assertWithMessage( false, "Unknown type" ); + return "Unknown"; + } + else + return gmsGdxTypeText[i]; +} + +std::string valTypStr( const int i ) +{ + if( i < 0 || i >= GMS_VAL_MAX ) + { + assertWithMessage( false, "Unknown type" ); + return "Unknown"; + } + else + { + constexpr std::array valsTypTxt { "L", "M", "LO", "UP", "SCALE" }; + return valsTypTxt[i]; + + // std::string result { gmsValTypeText[i] }; + // result.erase( 0, 1 ); + // for( char &c: result ) c = static_cast( toupper( c ) ); + // return result; + } +} + +std::string varTypStr( const int i ) +{ + if( i < 0 || i >= GMS_VARTYPE_MAX ) + { + assertWithMessage( false, "Unknown type" ); + return "Unknown"; + } + else + { + constexpr std::array varsTypTxt { "unknown ", "binary ", "integer ", "positive", "negative", "free ", "sos1 ", "sos2 ", "semicont", "semiint " }; + return varsTypTxt[i]; + + // return gmsVarTypeText[i]; + } +} + +std::string specialValueStr( const int i ) +{ + if( i < 0 || i >= GMS_SVIDX_MAX ) + { + assertWithMessage( false, "Unknown type" ); + return "Unknown"; + } + else + { + constexpr std::array svTxt { "Undf", "NA", "+Inf", "-Inf", "Eps", "0", "AcroN" }; + return svTxt[i]; + + // return gmsSVText[i]; + } +} + +std::vector splitString( const std::string &string, const char delimiter ) +{ + std::vector tokens; + std::istringstream iss( string ); + std::string token; + while( getline( iss, token, delimiter ) ) + tokens.push_back( token ); + return tokens; +} + +bool canBeQuoted( const char *s, const size_t slen ) +{ + if( !s ) return false; + bool saw_single {}, saw_double {}; + for( int i {}; i < (int) slen; i++ ) + { + char Ch { s[i] }; + if( Ch == '\'' ) + { + if( saw_double ) return false; + saw_single = true; + } + else if( Ch == '\"' ) + { + if( saw_single ) return false; + saw_double = true; + } + else if( static_cast( Ch ) < ' ' ) + return false; + } + return true; +} + +bool goodUELString( const char *s, const size_t slen ) +{ + return slen < GLOBAL_UEL_IDENT_SIZE && canBeQuoted( s, slen ); +} + +void AuditLine::setAuditLine() +{ + std::string + GDL_REL_PLT, + GDL_BLD_COD; + bool auditreldates_header_file_found {}; + + audit_line = system_name; + this->audit_line.resize( 17, ' ' ); + +#if defined( _WIN32 ) + GDL_REL_PLT = "x86 64bit/MS Windows"; + GDL_BLD_COD = "WEI"; +#elif defined( __APPLE__ ) +#if !defined( __arm64__ ) + GDL_REL_PLT = "x86 64bit/macOS"; + GDL_BLD_COD = "DEG"; +#else + GDL_REL_PLT = "arm 64bit/macOS"; + GDL_BLD_COD = "DAC"; +#endif +#elif defined( __linux__ ) + GDL_REL_PLT = "x86 64bit/Linux"; + GDL_BLD_COD = "LEG"; +#endif + +#if defined( __has_include ) +#if __has_include( "../../../../../../../btree/global/auditreldates.h" ) +#include "../../../../../../../btree/global/auditreldates.h" + auditreldates_header_file_found = true; + audit_line += std::to_string( GDL_REL_MAJ ) + '.' + std::to_string( GDL_REL_MIN ) + '.' + std::to_string( GDL_REL_GOLD ) + ' ' + GDL_REVISION + ' ' + GDL_REL_DAT; +#endif +#endif + + if( !auditreldates_header_file_found ) + { + std::time_t time { std::time( nullptr ) }; + std::tm *localtime { std::localtime( &time ) }; + std::stringstream date; + date << std::put_time( localtime, "%b %d, %Y" ); + audit_line += "??.?.? ???????? " + date.str(); + } + + audit_line.resize( 55, ' ' ); + audit_line += GDL_BLD_COD + ' ' + GDL_REL_PLT; +} + +AuditLine::AuditLine( const std::string &system_name ) +{ + setSystemName( system_name ); +} + +void AuditLine::setSystemName( const std::string &system_name ) +{ + this->system_name = system_name; + setAuditLine(); +} + +std::string AuditLine::getAuditLine() const +{ + return audit_line; +} + +}// namespace library diff --git a/src/tools/library/common.h b/src/tools/library/common.h new file mode 100644 index 0000000..82ef06d --- /dev/null +++ b/src/tools/library/common.h @@ -0,0 +1,46 @@ +#ifndef GDX_LIBRARY_H +#define GDX_LIBRARY_H + +#include +#include + +namespace library +{ + +void printErrorMessage( const std::string &message, bool printError = false ); + +void assertWithMessage( bool expression, const std::string &message ); + +std::string gdxSpecialValuesStr( int i ); + +std::string gdxDataTypStr( int i ); + +std::string gdxDataTypStrL( int i ); + +std::string valTypStr( int i ); + +std::string varTypStr( int i ); + +std::string specialValueStr( int i ); + +std::vector splitString( const std::string &string, char delimiter ); + +bool canBeQuoted( const char *s, size_t slen ); + +bool goodUELString( const char *s, size_t slen ); + +class AuditLine +{ + std::string system_name; + std::string audit_line; + void setAuditLine(); + +public: + AuditLine( const std::string &system_name ); + void setSystemName( const std::string &system_name ); + std::string getAuditLine() const; +}; + +}// namespace library + +#endif//GDX_LIBRARY_H diff --git a/src/tools/library/short_string.cpp b/src/tools/library/short_string.cpp new file mode 100644 index 0000000..b8b063f --- /dev/null +++ b/src/tools/library/short_string.cpp @@ -0,0 +1,188 @@ +#include +#include +#include + +#include "short_string.h" +#include "../../gdlib/utils.h" + +// #define ENABLE_ASSERTIONS + +namespace library +{ + +short_string::short_string() +{ + buffer.front() = '\0'; +} + +short_string::short_string( const char *s ) +{ + const size_t s_length { std::strlen( s ) }; +#if defined( ENABLE_ASSERTIONS ) + assert( s_length < MAX_LENGTH ); +#endif + std::memcpy( buffer.data(), s, s_length ); + buffer[s_length] = '\0'; +} + +short_string::short_string( const std::string &s ) : short_string( s.data() ) {} + +char *short_string::data() +{ + return buffer.data(); +} + +const char *short_string::data() const +{ + return buffer.data(); +} + +std::string short_string::string() const +{ + return buffer.data(); +} + +uint8_t short_string::length() const +{ + return static_cast( strlen( buffer.data() ) ); +} + +char short_string::front() const +{ + const char c { buffer.front() }; +#if defined( ENABLE_ASSERTIONS ) + assert( c != '\0' ); +#endif + return c; +} + +char short_string::back() const +{ + const uint8_t length { this->length() }; +#if defined( ENABLE_ASSERTIONS ) + assert( length > 0 ); +#endif + return buffer[length - 1]; +} + +char short_string::at( const uint8_t i ) const +{ +#if defined( ENABLE_ASSERTIONS ) + [[maybe_unused]] const uint8_t length { this->length() }; + if( i > 0 ) + assert( i < length ); + else + assert( i <= length ); +#endif + return buffer[i]; +} + +char short_string::operator[]( const uint8_t i ) const +{ + return buffer[i]; +} + +bool short_string::empty() const +{ + return buffer.front() == '\0'; +} + +void short_string::clear() +{ + buffer.front() = '\0'; +} + +void short_string::append( const char c ) +{ + const uint8_t length { this->length() }; +#if defined( ENABLE_ASSERTIONS ) + assert( length + 1 < MAX_LENGTH ); +#endif + buffer[length] = c; + buffer[length + 1] = '\0'; +} + +void short_string::append( const char *s ) +{ + const uint8_t length { this->length() }; + const size_t s_length { std::strlen( s ) }; +#if defined( ENABLE_ASSERTIONS ) + assert( length + s_length < MAX_LENGTH ); +#endif + std::memcpy( buffer.data() + length, s, s_length ); + buffer[length + s_length] = '\0'; +} + +void short_string::append( const short_string &s ) +{ + append( s.data() ); +} + +void short_string::append( const std::string &s ) +{ + append( s.data() ); +} + +void short_string::operator+=( const char c ) +{ + append( c ); +} + +void short_string::operator+=( const char *s ) +{ + append( s ); +} + +void short_string::operator+=( const short_string &s ) +{ + append( s ); +} + +void short_string::operator+=( const std::string &s ) +{ + append( s ); +} + +void short_string::to_upper_case() +{ + for( uint8_t i {}; buffer.at( i ) != '\0'; i++ ) + buffer[i] = static_cast( utils::toupper( buffer[i] ) ); +} + +short_string &short_string::operator=( const std::string &s ) +{ + const size_t s_length { s.length() }; +#if defined( ENABLE_ASSERTIONS ) + assert( s_length < MAX_LENGTH ); +#endif + std::memcpy( buffer.data(), s.data(), s_length ); + buffer[s_length] = '\0'; + return *this; +} + +bool short_string::operator==( const char *s ) const +{ + return strcmp( buffer.data(), s ) == 0; +} + +bool short_string::operator==( const short_string &s ) const +{ + return *this == s.data(); +} + +bool short_string::operator==( const std::string &s ) const +{ + return *this == s.data(); +} + +bool short_string::operator<( const short_string &s ) const +{ + return strcmp( buffer.data(), s.data() ) < 0; +} + +std::ostream &operator<<( std::ostream &os, const short_string &s ) +{ + return os << s.string(); +} + +}// namespace library diff --git a/src/tools/library/short_string.h b/src/tools/library/short_string.h new file mode 100644 index 0000000..88b0be1 --- /dev/null +++ b/src/tools/library/short_string.h @@ -0,0 +1,64 @@ +#ifndef GDX_SHORT_STRING_H +#define GDX_SHORT_STRING_H + +#include +#include +#include + +#include "../../../generated/gclgms.h" + +namespace library +{ + +class short_string +{ + static constexpr uint16_t MAX_LENGTH { GMS_SSSIZE }; + std::array buffer; + +public: + short_string(); + explicit short_string( const char *s ); + explicit short_string( const std::string &s ); + + char *data(); + [[nodiscard]] const char *data() const; + [[nodiscard]] std::string string() const; + + [[nodiscard]] uint8_t length() const; + + [[nodiscard]] char front() const; + [[nodiscard]] char back() const; + + char at( uint8_t i ) const; + char operator[]( uint8_t i ) const; + + [[nodiscard]] bool empty() const; + + void clear(); + + void append( char c ); + void append( const char *s ); + void append( const short_string &s ); + void append( const std::string &s ); + + void operator+=( char c ); + void operator+=( const char *s ); + void operator+=( const short_string &s ); + void operator+=( const std::string &s ); + + void to_upper_case(); + + short_string &operator=( const std::string &s ); + + bool operator==( const char *s ) const; + bool operator==( const short_string &s ) const; + bool operator==( const std::string &s ) const; + + bool operator<( const short_string &s ) const; + + friend std::ostream &operator<<( std::ostream &os, const short_string &s ); +}; + +}// namespace library + +#endif//GDX_SHORT_STRING_H diff --git a/src/tools/tests/.gitignore b/src/tools/tests/.gitignore new file mode 100644 index 0000000..cb2067d --- /dev/null +++ b/src/tools/tests/.gitignore @@ -0,0 +1,5 @@ +/venv/** +**/__pycache__/** +**/*.gdx +**/*.gsp +/results/*.md diff --git a/src/tools/tests/benchmark.py b/src/tools/tests/benchmark.py new file mode 100644 index 0000000..f0dc0bd --- /dev/null +++ b/src/tools/tests/benchmark.py @@ -0,0 +1,90 @@ +import platform +import subprocess +import os +import sys + +from examples.small_example import create_small_example +from examples.full_example import create_full_example + + +TESTS_DIRECTORY_PATH = os.path.dirname(os.path.abspath(__file__)) +DIRECTORY_PATHS = { + 'examples': os.path.join(TESTS_DIRECTORY_PATH, 'examples'), + 'results': os.path.join(TESTS_DIRECTORY_PATH, 'results') +} +FILE_NAMES = [ + 'small_example', + 'full_example', + 'diff_file', + 'merge_file' +] +FILE_PATHS = { + file_name: os.path.join( + DIRECTORY_PATHS['examples'], + f'{file_name}.gdx' + ) for file_name in FILE_NAMES +} + + +def benchmark_executable(executable_name: str, command: list[str]) -> None: + if platform.system() == 'Windows': + EXECUTABLE_PATH = ['Release', f'{executable_name}.exe'] + else: + EXECUTABLE_PATH = ['build', executable_name] + os.makedirs(DIRECTORY_PATHS['results'], exist_ok=True) + full_command = [ + 'hyperfine', + '--shell=none', + '--warmup', '5', + '--ignore-failure', + '--export-markdown', os.path.join( + DIRECTORY_PATHS['results'], + f'{executable_name}.md' + ), + '--command-name', f'{executable_name} (C++)', + ' '.join([ + os.path.join(TESTS_DIRECTORY_PATH, '..', '..', '..', *EXECUTABLE_PATH), + *command + ]), + '--command-name', f'{executable_name} (Delphi)', + ' '.join([ + executable_name, + *command + ]) + ] + print(f'{' '.join(full_command)}\n') + subprocess.run(full_command) + + +def main() -> int: + create_small_example(FILE_PATHS['small_example']) + create_full_example(FILE_PATHS['full_example']) + + executables: dict[str, list[str]] = { + 'gdxdump': [ + FILE_PATHS['full_example'] + ], + 'gdxdiff': [ + FILE_PATHS['small_example'], + FILE_PATHS['full_example'], + FILE_PATHS['diff_file'] + ], + 'gdxmerge': [ + FILE_PATHS['small_example'], + FILE_PATHS['full_example'], + f'Output={FILE_PATHS['merge_file']}' + ] + } + for i, executable_name in enumerate(executables): + benchmark_executable(executable_name, executables[executable_name]) + if i < len(executables) - 1: + print('\n') + + for file_path in FILE_PATHS.values(): + os.remove(file_path) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/tools/tests/environments/conda/environment.yml b/src/tools/tests/environments/conda/environment.yml new file mode 100644 index 0000000..0c280a4 --- /dev/null +++ b/src/tools/tests/environments/conda/environment.yml @@ -0,0 +1,8 @@ +name: gdxtools + +dependencies: + - python=3.12.4 + - pip + + - pip: + - gamsapi[transfer]==47.6.0 diff --git a/src/tools/tests/environments/conda/install.sh b/src/tools/tests/environments/conda/install.sh new file mode 100755 index 0000000..dccd27d --- /dev/null +++ b/src/tools/tests/environments/conda/install.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +conda env create --file "$(dirname $0)/environment.yml" diff --git a/src/tools/tests/environments/venv/install.sh b/src/tools/tests/environments/venv/install.sh new file mode 100755 index 0000000..17a7bd6 --- /dev/null +++ b/src/tools/tests/environments/venv/install.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +DIRECTORY_PATH="$(dirname $0)" + +python3 -m venv "$DIRECTORY_PATH/../../venv" +source "$DIRECTORY_PATH/../../venv/bin/activate" + +python3 -m pip install --upgrade pip +python3 -m pip install -r "$DIRECTORY_PATH/requirements.txt" diff --git a/src/tools/tests/environments/venv/requirements.txt b/src/tools/tests/environments/venv/requirements.txt new file mode 100644 index 0000000..4f1c2e9 --- /dev/null +++ b/src/tools/tests/environments/venv/requirements.txt @@ -0,0 +1 @@ +gamsapi[transfer]==47.6.0 diff --git a/src/tools/tests/examples/default_values_examples.py b/src/tools/tests/examples/default_values_examples.py new file mode 100644 index 0000000..bcf3d36 --- /dev/null +++ b/src/tools/tests/examples/default_values_examples.py @@ -0,0 +1,37 @@ +import gams.transfer as gt +import pandas as pd + + +def create_default_values_example_1(file_path: str) -> None: + m = gt.Container() + + gt.Variable( + m, + 'v', + 'free', + domain=['*'], + records=pd.DataFrame( + data=[('i' + str(i), i) for i in range(5)], + columns=['domain', 'marginal'] + ) + ) + + m.write(file_path) + + +def create_default_values_example_2(file_path: str) -> None: + m = gt.Container() + + v = gt.Variable( + m, + 'v', + 'free', + domain=['*'], + records=pd.DataFrame( + data=[('i' + str(i), i) for i in range(5)], + columns=['domain', 'marginal'] + ) + ) + v.records.drop(0, inplace=True) + + m.write(file_path) diff --git a/src/tools/tests/examples/description_examples.py b/src/tools/tests/examples/description_examples.py new file mode 100644 index 0000000..79e0d3e --- /dev/null +++ b/src/tools/tests/examples/description_examples.py @@ -0,0 +1,17 @@ +import gams.transfer as gt + + +def create_description_example_1(file_path: str) -> None: + m = gt.Container() + + gt.Set(m, 'i', records=[('seattle', 'text 1'), ('san-diego', 'text 2')]) + + m.write(file_path) + + +def create_description_example_2(file_path: str) -> None: + m = gt.Container() + + gt.Set(m, 'i', records=[('seattle', 'text 3'), ('san-diego', 'text 4')]) + + m.write(file_path) diff --git a/src/tools/tests/examples/domain_examples.py b/src/tools/tests/examples/domain_examples.py new file mode 100644 index 0000000..7030ae4 --- /dev/null +++ b/src/tools/tests/examples/domain_examples.py @@ -0,0 +1,19 @@ +import gams.transfer as gt + + +def create_domain_example_1(file_path: str) -> None: + m = gt.Container() + + i = gt.Set(m, 'i', records=['i1', 'i2']) + gt.Variable(m, 'a', domain=[i]) + + m.write(file_path) + + +def create_domain_example_2(file_path: str) -> None: + m = gt.Container() + + j = gt.Set(m, 'j', records=['i1', 'i2']) + gt.Variable(m, 'a', domain=[j]) + + m.write(file_path) diff --git a/src/tools/tests/examples/element_text_example.py b/src/tools/tests/examples/element_text_example.py new file mode 100644 index 0000000..62229bd --- /dev/null +++ b/src/tools/tests/examples/element_text_example.py @@ -0,0 +1,32 @@ +import gams.transfer as gt +import pandas as pd + + +def create_element_text_example(file_path: str) -> None: + m = gt.Container() + + # create the sets i, j + i = gt.Set(m, 'i', records=[('seattle', 'text 1'), ('san-diego', 'text 2')], description='supply') + j = gt.Set(m, 'j', records=[('new-york', 'text 3'), ('chicago', 'text 4'), ('topeka', 'text 5')], description='markets') + + # add 'd' parameter -- domain linked to set objects i and j + d = gt.Parameter(m, 'd', [i, j], description='distance in thousands of miles') + + # create some data as a generic DataFrame + dist = pd.DataFrame( + [ + ('seattle', 'new-york', 2.5), + ('seattle', 'chicago', 1.7), + ('seattle', 'topeka', 1.8), + ('san-diego', 'new-york', 2.5), + ('san-diego', 'chicago', 1.8), + ('san-diego', 'topeka', 1.4), + ], + columns=['from', 'to', 'thousand_miles'], + ) + + # setRecords will automatically convert the dist DataFrame into a standard DataFrame format + d.setRecords(dist) + + # write the GDX + m.write(file_path) diff --git a/src/tools/tests/examples/full_example.py b/src/tools/tests/examples/full_example.py new file mode 100644 index 0000000..200d3b6 --- /dev/null +++ b/src/tools/tests/examples/full_example.py @@ -0,0 +1,103 @@ +import gams.transfer as gt +import pandas as pd + + +def create_full_example(file_path: str) -> None: + # create an empty Container object + m = gt.Container() + + # add sets + i = gt.Set(m, 'i', records=['seattle', 'san-diego'], description='supply') + j = gt.Set(m, 'j', records=['new-york', 'chicago', 'topeka'], description='markets') + + # add parameters + a = gt.Parameter(m, 'a', ['*'], description='capacity of plant i in cases') + b = gt.Parameter(m, 'b', [j], description='demand at market j in cases') + d = gt.Parameter(m, 'd', [i, j], description='distance in thousands of miles') + f = gt.Parameter( + m, 'f', records=90, description='freight in dollars per case per thousand miles' + ) + c = gt.Parameter( + m, 'c', [i, j], description='transport cost in thousands of dollars per case' + ) + + # set parameter records + cap = pd.DataFrame([('seattle', 350), ('san-diego', 600)], columns=['plant', 'n_cases']) + a.setRecords(cap) + + dem = pd.DataFrame( + [('new-york', 325), ('chicago', 300), ('topeka', 275)], + columns=['market', 'n_cases'], + ) + b.setRecords(dem) + + dist = pd.DataFrame( + [ + ('seattle', 'new-york', 2.5), + ('seattle', 'chicago', 1.7), + ('seattle', 'topeka', 1.8), + ('san-diego', 'new-york', 2.5), + ('san-diego', 'chicago', 1.8), + ('san-diego', 'topeka', 1.4), + ], + columns=['from', 'to', 'thousand_miles'], + ) + d.setRecords(dist) + + # c(i,j) = f * d(i,j) / 1000; + cost = d.records.copy(deep=True) + cost['value'] = f.records.loc[0, 'value'] * cost['value'] / 1000 + c.setRecords(cost) + + # add variables + q = pd.DataFrame( + [ + ('seattle', 'new-york', 50, 0), + ('seattle', 'chicago', 300, 0), + ('seattle', 'topeka', 0, 0.036), + ('san-diego', 'new-york', 275, 0), + ('san-diego', 'chicago', 0, 0.009), + ('san-diego', 'topeka', 275, 0), + ], + columns=['from', 'to', 'level', 'marginal'], + ) + gt.Variable( + m, 'x', 'positive', [i, j], records=q, description='shipment quantities in cases', + ) + gt.Variable( + m, + 'z', + records=pd.DataFrame(data=[153.675], columns=['level']), + description='total transportation costs in thousands of dollars', + ) + + # add equations + cost = gt.Equation(m, 'cost', 'eq', description='define objective function') + supply = gt.Equation(m, 'supply', 'leq', [i], description='observe supply limit at plant i') + demand = gt.Equation(m, 'demand', 'geq', [j], description='satisfy demand at market j') + + # set equation records + cost.setRecords( + pd.DataFrame(data=[[0, 1, 0, 0]], columns=['level', 'marginal', 'lower', 'upper']) + ) + + supplies = pd.DataFrame( + [ + ('seattle', 350, 'eps', float('-inf'), 350), + ('san-diego', 550, 0, float('-inf'), 600), + ], + columns=['from', 'level', 'marginal', 'lower', 'upper'], + ) + supply.setRecords(supplies) + + demands = pd.DataFrame( + [ + ('new-york', 325, 0.225, 325), + ('chicago', 300, 0.153, 300), + ('topeka', 275, 0.126, 275), + ], + columns=['from', 'level', 'marginal', 'lower'], + ) + demand.setRecords(demands) + + m.write(file_path) diff --git a/src/tools/tests/examples/full_example_changed_data_and_variables.py b/src/tools/tests/examples/full_example_changed_data_and_variables.py new file mode 100644 index 0000000..69d5e87 --- /dev/null +++ b/src/tools/tests/examples/full_example_changed_data_and_variables.py @@ -0,0 +1,103 @@ +import gams.transfer as gt +import pandas as pd + + +def create_full_example_changed_data_and_variables(file_path: str) -> None: + # create an empty Container object + m = gt.Container() + + # add sets + i = gt.Set(m, 'i', records=['seattle', 'san-diego'], description='supply') + j = gt.Set(m, 'j', records=['new-york', 'chicago', 'topeka'], description='markets') + + # add parameters + a = gt.Parameter(m, 'a', ['*'], description='capacity of plant i in cases') + b = gt.Parameter(m, 'b', [j], description='demand at market j in cases') + d = gt.Parameter(m, 'd', [i, j], description='distance in thousands of miles') + f = gt.Parameter( + m, 'f', records=90, description='freight in dollars per case per thousand miles' + ) + c = gt.Parameter( + m, 'c', [i, j], description='transport cost in thousands of dollars per case' + ) + + # set parameter records + cap = pd.DataFrame([('seattle', 350), ('san-diego', 600)], columns=['plant', 'n_cases']) + a.setRecords(cap) + + dem = pd.DataFrame( + [('new-york', 325), ('chicago', 300), ('topeka', 275)], + columns=['market', 'n_cases'], + ) + b.setRecords(dem) + + dist = pd.DataFrame( + [ + ('seattle', 'new-york', 3.5), + ('seattle', 'chicago', 2.7), + ('seattle', 'topeka', 2.8), + ('san-diego', 'new-york', 3.5), + ('san-diego', 'chicago', 2.8), + ('san-diego', 'topeka', 2.4), + ], + columns=['from', 'to', 'thousand_miles'], + ) + d.setRecords(dist) + + # c(i,j) = f * d(i,j) / 1000; + cost = d.records.copy(deep=True) + cost['value'] = f.records.loc[0, 'value'] * cost['value'] / 1000 + c.setRecords(cost) + + # add variables + q = pd.DataFrame( + [ + ('seattle', 'new-york', 150, 0), + ('seattle', 'chicago', 400, 0), + ('seattle', 'topeka', 0, 0.036), + ('san-diego', 'new-york', 375, 0), + ('san-diego', 'chicago', 0, 0.009), + ('san-diego', 'topeka', 375, 0), + ], + columns=['from', 'to', 'level', 'marginal'], + ) + gt.Variable( + m, 'x', 'positive', [i, j], records=q, description='shipment quantities in cases', + ) + gt.Variable( + m, + 'z', + records=pd.DataFrame(data=[153.675], columns=['level']), + description='total transportation costs in thousands of dollars', + ) + + # add equations + cost = gt.Equation(m, 'cost', 'eq', description='define objective function') + supply = gt.Equation(m, 'supply', 'leq', [i], description='observe supply limit at plant i') + demand = gt.Equation(m, 'demand', 'geq', [j], description='satisfy demand at market j') + + # set equation records + cost.setRecords( + pd.DataFrame(data=[[0, 1, 0, 0]], columns=['level', 'marginal', 'lower', 'upper']) + ) + + supplies = pd.DataFrame( + [ + ('seattle', 350, 'eps', float('-inf'), 350), + ('san-diego', 550, 0, float('-inf'), 600), + ], + columns=['from', 'level', 'marginal', 'lower', 'upper'], + ) + supply.setRecords(supplies) + + demands = pd.DataFrame( + [ + ('new-york', 325, 0.225, 325), + ('chicago', 300, 0.153, 300), + ('topeka', 275, 0.126, 275), + ], + columns=['from', 'level', 'marginal', 'lower'], + ) + demand.setRecords(demands) + + m.write(file_path) diff --git a/src/tools/tests/examples/full_example_changed_variables.py b/src/tools/tests/examples/full_example_changed_variables.py new file mode 100644 index 0000000..b016ec3 --- /dev/null +++ b/src/tools/tests/examples/full_example_changed_variables.py @@ -0,0 +1,103 @@ +import gams.transfer as gt +import pandas as pd + + +def create_full_example_changed_variables(file_path: str) -> None: + # create an empty Container object + m = gt.Container() + + # add sets + i = gt.Set(m, 'i', records=['seattle', 'san-diego'], description='supply') + j = gt.Set(m, 'j', records=['new-york', 'chicago', 'topeka'], description='markets') + + # add parameters + a = gt.Parameter(m, 'a', ['*'], description='capacity of plant i in cases') + b = gt.Parameter(m, 'b', [j], description='demand at market j in cases') + d = gt.Parameter(m, 'd', [i, j], description='distance in thousands of miles') + f = gt.Parameter( + m, 'f', records=90, description='freight in dollars per case per thousand miles' + ) + c = gt.Parameter( + m, 'c', [i, j], description='transport cost in thousands of dollars per case' + ) + + # set parameter records + cap = pd.DataFrame([('seattle', 350), ('san-diego', 600)], columns=['plant', 'n_cases']) + a.setRecords(cap) + + dem = pd.DataFrame( + [('new-york', 325), ('chicago', 300), ('topeka', 275)], + columns=['market', 'n_cases'], + ) + b.setRecords(dem) + + dist = pd.DataFrame( + [ + ('seattle', 'new-york', 2.5), + ('seattle', 'chicago', 1.7), + ('seattle', 'topeka', 1.8), + ('san-diego', 'new-york', 2.5), + ('san-diego', 'chicago', 1.8), + ('san-diego', 'topeka', 1.4), + ], + columns=['from', 'to', 'thousand_miles'], + ) + d.setRecords(dist) + + # c(i,j) = f * d(i,j) / 1000; + cost = d.records.copy(deep=True) + cost['value'] = f.records.loc[0, 'value'] * cost['value'] / 1000 + c.setRecords(cost) + + # add variables + q = pd.DataFrame( + [ + ('seattle', 'new-york', 150, 0), + ('seattle', 'chicago', 400, 0), + ('seattle', 'topeka', 0, 0.036), + ('san-diego', 'new-york', 375, 0), + ('san-diego', 'chicago', 0, 0.009), + ('san-diego', 'topeka', 375, 0), + ], + columns=['from', 'to', 'level', 'marginal'], + ) + gt.Variable( + m, 'x', 'positive', [i, j], records=q, description='shipment quantities in cases', + ) + gt.Variable( + m, + 'z', + records=pd.DataFrame(data=[153.675], columns=['level']), + description='total transportation costs in thousands of dollars', + ) + + # add equations + cost = gt.Equation(m, 'cost', 'eq', description='define objective function') + supply = gt.Equation(m, 'supply', 'leq', [i], description='observe supply limit at plant i') + demand = gt.Equation(m, 'demand', 'geq', [j], description='satisfy demand at market j') + + # set equation records + cost.setRecords( + pd.DataFrame(data=[[0, 1, 0, 0]], columns=['level', 'marginal', 'lower', 'upper']) + ) + + supplies = pd.DataFrame( + [ + ('seattle', 350, 'eps', float('-inf'), 350), + ('san-diego', 550, 0, float('-inf'), 600), + ], + columns=['from', 'level', 'marginal', 'lower', 'upper'], + ) + supply.setRecords(supplies) + + demands = pd.DataFrame( + [ + ('new-york', 325, 0.225, 325), + ('chicago', 300, 0.153, 300), + ('topeka', 275, 0.126, 275), + ], + columns=['from', 'level', 'marginal', 'lower'], + ) + demand.setRecords(demands) + + m.write(file_path) diff --git a/src/tools/tests/examples/label_example.py b/src/tools/tests/examples/label_example.py new file mode 100644 index 0000000..df85956 --- /dev/null +++ b/src/tools/tests/examples/label_example.py @@ -0,0 +1,50 @@ +import gams.transfer as gt +import pandas as pd + + +def get_test_string(count=255) -> str: + return f'|{'_' * (count - 2)}|' + + +def create_label_example(file_path: str) -> None: + m = gt.Container() + + # create the set i + i = gt.Set(m, 'i', records=[ + ('seattle', get_test_string()), + ('san-diego', get_test_string()) + ], description=get_test_string()) + + # create the set j + j = gt.Set(m, 'j', records=[ + ('new-york', get_test_string()), + ('chicago', get_test_string()), + ('topeka', get_test_string()) + ], description=get_test_string()) + + # add 'd' parameter -- domain linked to set objects i and j + d = gt.Parameter(m, 'd', [i, j], description=get_test_string()) + + # create some data as a generic DataFrame + dist = pd.DataFrame( + [ + ('seattle', 'new-york', 2.5), + ('seattle', 'chicago', 1.7), + ('seattle', 'topeka', 1.8), + ('san-diego', 'new-york', 2.5), + ('san-diego', 'chicago', 1.8), + ('san-diego', 'topeka', 1.4), + ], + # Probably unnecessary here: + columns=[ + get_test_string(), + get_test_string(), + get_test_string() + ] + ) + + # setRecords will automatically convert the dist DataFrame into a standard DataFrame format + d.setRecords(dist) + + # write the GDX + m.write(file_path) diff --git a/src/tools/tests/examples/order_examples.py b/src/tools/tests/examples/order_examples.py new file mode 100644 index 0000000..7d6c242 --- /dev/null +++ b/src/tools/tests/examples/order_examples.py @@ -0,0 +1,21 @@ +import gams.transfer as gt + + +def create_order_example_1(file_path: str) -> None: + m = gt.Container() + + gt.Set(m, 't1', records=[1987, 1988, 1989, 1990, 1991]) + gt.Set(m, 't2', records=[1983, 1984, 1985, 1986, 1987]) + gt.Set(m, 't3', records=[1987, 1989, 1991, 1983, 1985]) + + m.write(file_path) + + +def create_order_example_2(file_path: str) -> None: + m = gt.Container() + + gt.Set(m, 't2', records=[1987, 1988, 1989, 1990, 1991]) + gt.Set(m, 't3', records=[1983, 1984, 1985, 1986, 1987]) + gt.Set(m, 't1', records=[1987, 1989, 1991, 1983, 1985]) + + m.write(file_path) diff --git a/src/tools/tests/examples/small_example.py b/src/tools/tests/examples/small_example.py new file mode 100644 index 0000000..4d8a892 --- /dev/null +++ b/src/tools/tests/examples/small_example.py @@ -0,0 +1,32 @@ +import gams.transfer as gt +import pandas as pd + + +def create_small_example(file_path: str) -> None: + m = gt.Container() + + # create the sets i, j + i = gt.Set(m, 'i', records=['seattle', 'san-diego'], description='supply') + j = gt.Set(m, 'j', records=['new-york', 'chicago', 'topeka'], description='markets') + + # add 'd' parameter -- domain linked to set objects i and j + d = gt.Parameter(m, 'd', [i, j], description='distance in thousands of miles') + + # create some data as a generic DataFrame + dist = pd.DataFrame( + [ + ('seattle', 'new-york', 2.5), + ('seattle', 'chicago', 1.7), + ('seattle', 'topeka', 1.8), + ('san-diego', 'new-york', 2.5), + ('san-diego', 'chicago', 1.8), + ('san-diego', 'topeka', 1.4), + ], + columns=['from', 'to', 'thousand_miles'], + ) + + # setRecords will automatically convert the dist DataFrame into a standard DataFrame format + d.setRecords(dist) + + # write the GDX + m.write(file_path) diff --git a/src/tools/tests/examples/small_example_changed_data.py b/src/tools/tests/examples/small_example_changed_data.py new file mode 100644 index 0000000..eb6c6f5 --- /dev/null +++ b/src/tools/tests/examples/small_example_changed_data.py @@ -0,0 +1,32 @@ +import gams.transfer as gt +import pandas as pd + + +def create_small_example_changed_data(file_path: str) -> None: + m = gt.Container() + + # create the sets i, j + i = gt.Set(m, 'i', records=['seattle', 'san-diego'], description='supply') + j = gt.Set(m, 'j', records=['new-york', 'chicago', 'topeka'], description='markets') + + # add 'd' parameter -- domain linked to set objects i and j + d = gt.Parameter(m, 'd', [i, j], description='distance in thousands of miles') + + # create some data as a generic DataFrame + dist = pd.DataFrame( + [ + ('seattle', 'new-york', 3.5), + ('seattle', 'chicago', 2.7), + ('seattle', 'topeka', 2.8), + ('san-diego', 'new-york', 3.5), + ('san-diego', 'chicago', 2.8), + ('san-diego', 'topeka', 2.4), + ], + columns=['from', 'to', 'thousand_miles'], + ) + + # setRecords will automatically convert the dist DataFrame into a standard DataFrame format + d.setRecords(dist) + + # write the GDX + m.write(file_path) diff --git a/src/tools/tests/examples/special_values_example.py b/src/tools/tests/examples/special_values_example.py new file mode 100644 index 0000000..7f718ba --- /dev/null +++ b/src/tools/tests/examples/special_values_example.py @@ -0,0 +1,32 @@ +import gams.transfer as gt +import pandas as pd + + +def create_special_values_example(file_path: str) -> None: + m = gt.Container() + + # create the sets i, j + i = gt.Set(m, 'i', records=['seattle', 'san-diego'], description='supply') + j = gt.Set(m, 'j', records=['new-york', 'chicago', 'topeka'], description='markets') + + # add 'd' parameter -- domain linked to set objects i and j + d = gt.Parameter(m, 'd', [i, j], description='distance in thousands of miles') + + # create some data as a generic DataFrame + dist = pd.DataFrame( + [ + ('seattle', 'new-york', gt.SpecialValues.EPS), + ('seattle', 'chicago', gt.SpecialValues.NA), + ('seattle', 'topeka', gt.SpecialValues.POSINF), + ('san-diego', 'new-york', gt.SpecialValues.NEGINF), + ('san-diego', 'chicago', gt.SpecialValues.UNDEF), + ('san-diego', 'topeka', 0), + ], + columns=['from', 'to', 'thousand_miles'], + ) + + # setRecords will automatically convert the dist DataFrame into a standard DataFrame format + d.setRecords(dist) + + # write the GDX + m.write(file_path, eps_to_zero=False) diff --git a/src/tools/tests/gdxdiff.py b/src/tools/tests/gdxdiff.py new file mode 100644 index 0000000..b1fa107 --- /dev/null +++ b/src/tools/tests/gdxdiff.py @@ -0,0 +1,1051 @@ +import unittest +import os +import platform +import subprocess +import inspect +import gams.transfer as gt + +from examples.small_example import create_small_example +from examples.full_example import create_full_example +from examples.small_example_changed_data import create_small_example_changed_data +from examples.full_example_changed_variables import create_full_example_changed_variables +from examples.full_example_changed_data_and_variables import create_full_example_changed_data_and_variables +from examples.default_values_examples import create_default_values_example_1, create_default_values_example_2 +from examples.domain_examples import create_domain_example_1, create_domain_example_2 +from examples.order_examples import create_order_example_1, create_order_example_2 +from examples.description_examples import create_description_example_1, create_description_example_2 + + +class TestGdxDiff(unittest.TestCase): + TESTS_DIRECTORY_PATH = os.path.dirname(os.path.abspath(__file__)) + DIRECTORY_PATHS = { + 'examples': os.path.join(TESTS_DIRECTORY_PATH, 'examples'), + 'output': os.path.join(TESTS_DIRECTORY_PATH, 'output', 'gdxdiff') + } + FILE_NAMES = [ + 'small_example', + 'full_example', + 'small_example_changed_data', + 'full_example_changed_variables', + 'full_example_changed_data_and_variables', + 'default_values_example_1', 'default_values_example_2', + 'domain_example_1', 'domain_example_2', + 'order_example_1', 'order_example_2', + 'description_example_1', 'description_example_2', + 'diff_file' + ] + FILE_PATHS: dict[str, str] + + @classmethod + def setUpClass(cls) -> None: + cls.FILE_PATHS = { + file_name: os.path.join( + cls.DIRECTORY_PATHS['examples'], + f'{file_name}.gdx' + ) for file_name in cls.FILE_NAMES + } + + create_small_example(cls.FILE_PATHS['small_example']) + create_full_example(cls.FILE_PATHS['full_example']) + create_small_example_changed_data(cls.FILE_PATHS['small_example_changed_data']) + create_full_example_changed_variables(cls.FILE_PATHS['full_example_changed_variables']) + create_full_example_changed_data_and_variables(cls.FILE_PATHS['full_example_changed_data_and_variables']) + create_default_values_example_1(cls.FILE_PATHS['default_values_example_1']) + create_default_values_example_2(cls.FILE_PATHS['default_values_example_2']) + create_domain_example_1(cls.FILE_PATHS['domain_example_1']) + create_domain_example_2(cls.FILE_PATHS['domain_example_2']) + create_order_example_1(cls.FILE_PATHS['order_example_1']) + create_order_example_2(cls.FILE_PATHS['order_example_2']) + create_description_example_1(cls.FILE_PATHS['description_example_1']) + create_description_example_2(cls.FILE_PATHS['description_example_2']) + + @classmethod + def tearDownClass(cls) -> None: + for file_path in cls.FILE_PATHS.values(): + os.remove(file_path) + + @classmethod + def run_gdxdiff(cls, command: list[str]) -> subprocess.CompletedProcess[str]: + EXECUTABLE_NAME = 'gdxdiff' + if platform.system() == 'Windows': + EXECUTABLE_PATH = ['Release', f'{EXECUTABLE_NAME}.exe'] + else: + EXECUTABLE_PATH = ['build', EXECUTABLE_NAME] + return subprocess.run( + [os.path.join(cls.TESTS_DIRECTORY_PATH, '..', '..', '..', *EXECUTABLE_PATH), *command], + capture_output=True, + text=True + ) + + def check_output( + self, + output: subprocess.CompletedProcess[str], + return_code=0, + file_name: str | None = None, + first_offset: int | None = None, + first_negative_offset: int | None = None, + second_offset: int | None = None, + second_negative_offset: int | None = None, + first_delete: list[int] = [], + second_delete: list[int] = [] + ) -> None: + self.assertEqual(output.returncode, return_code) + first = output.stdout.split('\n')[first_offset:first_negative_offset] + for i in first_delete: + del first[i] + if file_name is None: + file_name = f'{inspect.stack()[1].function.removeprefix('test_')}.txt' + with open(os.path.join(self.DIRECTORY_PATHS['output'], file_name), 'r') as file: + second = file.read().split('\n')[second_offset:second_negative_offset] + for i in second_delete: + del second[i] + self.assertEqual(first, second) + self.assertEqual(output.stderr, '') + + def check_gdx_file_symbols( + self, + container: gt.Container, + symbol_names: list[str] + ) -> None: + for symbol_name in symbol_names: + with self.subTest(symbol_name=symbol_name): + self.assertIn(symbol_name, container) + self.assertEqual(len(container), len(symbol_names)) + + def check_gdx_file_values( + self, + container: gt.Container, + symbol_name: str, + expected_values: list[list[str | float]] + ) -> None: + self.assertIn(symbol_name, container) + symbol: gt.Parameter = container[symbol_name] # type: ignore + values = symbol.records.values.tolist() + self.assertEqual(values, expected_values) + + def check_gdx_file( + self, + symbols: dict[str, list[list[str | float]]] + ) -> None: + container = gt.Container(load_from=self.FILE_PATHS['diff_file']) + self.check_gdx_file_symbols(container, list(symbols.keys())) + for symbol_name in symbols: + self.check_gdx_file_values(container, symbol_name, symbols[symbol_name]) + + def test_empty_command(self) -> None: + output = self.run_gdxdiff([]) + self.check_output( + output, + return_code=2, + file_name='usage.txt', + second_offset=1, + first_delete=[1], + second_delete=[1] + ) + + def test_small_example_and_full_example(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['full_example'], + self.FILE_PATHS['diff_file'] + ]) + self.check_output( + output, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'FilesCompared': [ + ['File1', self.FILE_PATHS['small_example']], + ['File2', self.FILE_PATHS['full_example']] + ] + } + self.check_gdx_file(symbols) + + def test_small_example_and_small_example_changed_data(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['small_example_changed_data'], + self.FILE_PATHS['diff_file'] + ]) + self.check_output( + output, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'd': [ + ['seattle', 'new-york', 'dif1', 2.5], + ['seattle', 'new-york', 'dif2', 3.5], + ['seattle', 'chicago', 'dif1', 1.7], + ['seattle', 'chicago', 'dif2', 2.7], + ['seattle', 'topeka', 'dif1', 1.8], + ['seattle', 'topeka', 'dif2', 2.8], + ['san-diego', 'new-york', 'dif1', 2.5], + ['san-diego', 'new-york', 'dif2', 3.5], + ['san-diego', 'chicago', 'dif1', 1.8], + ['san-diego', 'chicago', 'dif2', 2.8], + ['san-diego', 'topeka', 'dif1', 1.4], + ['san-diego', 'topeka', 'dif2', 2.4] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['small_example']], + ['File2', self.FILE_PATHS['small_example_changed_data']] + ] + } + self.check_gdx_file(symbols) + + def test_small_example_and_small_example_changed_data_epsilon_absolute_1(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['small_example_changed_data'], + self.FILE_PATHS['diff_file'], + 'Eps=1' + ]) + self.check_output( + output, + return_code=1, + file_name='small_example_and_small_example_changed_data.txt', + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'd': [ + ['seattle', 'chicago', 'dif1', 1.7], + ['seattle', 'chicago', 'dif2', 2.7] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['small_example']], + ['File2', self.FILE_PATHS['small_example_changed_data']] + ] + } + self.check_gdx_file(symbols) + + def test_small_example_and_small_example_changed_data_epsilon_absolute_2(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['small_example_changed_data'], + self.FILE_PATHS['diff_file'], + 'Eps=2' + ]) + self.check_output( + output, + return_code=0, + file_name='small_example_and_small_example_changed_data_epsilon.txt', + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'FilesCompared': [ + ['File1', self.FILE_PATHS['small_example']], + ['File2', self.FILE_PATHS['small_example_changed_data']] + ] + } + self.check_gdx_file(symbols) + + def test_small_example_and_small_example_changed_data_epsilon_relative(self) -> None: + for i in [1, 2]: + with self.subTest(i=i): + output = self.run_gdxdiff([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['small_example_changed_data'], + self.FILE_PATHS['diff_file'], + f'RelEps={i}' + ]) + self.check_output( + output, + return_code=0, + file_name='small_example_and_small_example_changed_data_epsilon.txt', + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'FilesCompared': [ + ['File1', self.FILE_PATHS['small_example']], + ['File2', self.FILE_PATHS['small_example_changed_data']] + ] + } + self.check_gdx_file(symbols) + + def test_full_example_and_full_example_changed_variables(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_variables'], + self.FILE_PATHS['diff_file'] + ]) + self.check_output( + output, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'x': [ + ['seattle', 'new-york', 'dif1', 50.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'new-york', 'dif2', 150.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'chicago', 'dif1', 300.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'chicago', 'dif2', 400.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'new-york', 'dif1', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'new-york', 'dif2', 375.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'topeka', 'dif1', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'topeka', 'dif2', 375.0, 0.0, 0.0, float('inf'), 1.0] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['full_example']], + ['File2', self.FILE_PATHS['full_example_changed_variables']] + ] + } + self.check_gdx_file(symbols) + + def test_full_example_and_full_example_changed_variables_field_all(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_variables'], + self.FILE_PATHS['diff_file'], + 'Field=All' + ]) + self.check_output( + output, + return_code=1, + file_name='full_example_and_full_example_changed_variables.txt', + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'x': [ + ['seattle', 'new-york', 'dif1', 50.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'new-york', 'dif2', 150.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'chicago', 'dif1', 300.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'chicago', 'dif2', 400.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'new-york', 'dif1', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'new-york', 'dif2', 375.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'topeka', 'dif1', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'topeka', 'dif2', 375.0, 0.0, 0.0, float('inf'), 1.0] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['full_example']], + ['File2', self.FILE_PATHS['full_example_changed_variables']] + ] + } + self.check_gdx_file(symbols) + + def test_full_example_and_full_example_changed_variables_field_l(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_variables'], + self.FILE_PATHS['diff_file'], + 'Field=L' + ]) + self.check_output( + output, + return_code=1, + file_name='full_example_and_full_example_changed_variables.txt', + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'x': [ + ['seattle', 'new-york', 'dif1', 50.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'new-york', 'dif2', 150.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'chicago', 'dif1', 300.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'chicago', 'dif2', 400.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'new-york', 'dif1', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'new-york', 'dif2', 375.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'topeka', 'dif1', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'topeka', 'dif2', 375.0, 0.0, 0.0, float('inf'), 1.0] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['full_example']], + ['File2', self.FILE_PATHS['full_example_changed_variables']] + ] + } + self.check_gdx_file(symbols) + + def test_full_example_and_full_example_changed_variables_field_m(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_variables'], + self.FILE_PATHS['diff_file'], + 'Field=M' + ]) + self.check_output( + output, + return_code=0, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'FilesCompared': [ + ['File1', self.FILE_PATHS['full_example']], + ['File2', self.FILE_PATHS['full_example_changed_variables']] + ] + } + self.check_gdx_file(symbols) + + def test_full_example_and_full_example_changed_variables_field_l_field_only(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_variables'], + self.FILE_PATHS['diff_file'], + 'Field=L', + 'FldOnly' + ]) + self.check_output( + output, + return_code=1, + file_name='full_example_and_full_example_changed_variables.txt', + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'x': [ + ['seattle', 'new-york', 'dif1', 50.0], + ['seattle', 'new-york', 'dif2', 150.0], + ['seattle', 'chicago', 'dif1', 300.0], + ['seattle', 'chicago', 'dif2', 400.0], + ['san-diego', 'new-york', 'dif1', 275.0], + ['san-diego', 'new-york', 'dif2', 375.0], + ['san-diego', 'topeka', 'dif1', 275.0], + ['san-diego', 'topeka', 'dif2', 375.0] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['full_example']], + ['File2', self.FILE_PATHS['full_example_changed_variables']] + ] + } + self.check_gdx_file(symbols) + + def test_full_example_and_full_example_changed_variables_field_m_field_only(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_variables'], + self.FILE_PATHS['diff_file'], + 'Field=M', + 'FldOnly' + ]) + self.check_output( + output, + return_code=0, + file_name='full_example_and_full_example_changed_variables_field_m.txt', + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'FilesCompared': [ + ['File1', self.FILE_PATHS['full_example']], + ['File2', self.FILE_PATHS['full_example_changed_variables']] + ] + } + self.check_gdx_file(symbols) + + def test_full_example_and_full_example_changed_data_and_variables(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_data_and_variables'], + self.FILE_PATHS['diff_file'] + ]) + self.check_output( + output, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'c': [ + ['seattle', 'new-york', 'dif1', 0.225], + ['seattle', 'new-york', 'dif2', 0.315], + ['seattle', 'chicago', 'dif1', 0.153], + ['seattle', 'chicago', 'dif2', 0.24300000000000002], + ['seattle', 'topeka', 'dif1', 0.162], + ['seattle', 'topeka', 'dif2', 0.25199999999999995], + ['san-diego', 'new-york', 'dif1', 0.225], + ['san-diego', 'new-york', 'dif2', 0.315], + ['san-diego', 'chicago', 'dif1', 0.162], + ['san-diego', 'chicago', 'dif2', 0.25199999999999995], + ['san-diego', 'topeka', 'dif1', 0.12599999999999997], + ['san-diego', 'topeka', 'dif2', 0.216] + ], + 'd': [ + ['seattle', 'new-york', 'dif1', 2.5], + ['seattle', 'new-york', 'dif2', 3.5], + ['seattle', 'chicago', 'dif1', 1.7], + ['seattle', 'chicago', 'dif2', 2.7], + ['seattle', 'topeka', 'dif1', 1.8], + ['seattle', 'topeka', 'dif2', 2.8], + ['san-diego', 'new-york', 'dif1', 2.5], + ['san-diego', 'new-york', 'dif2', 3.5], + ['san-diego', 'chicago', 'dif1', 1.8], + ['san-diego', 'chicago', 'dif2', 2.8], + ['san-diego', 'topeka', 'dif1', 1.4], + ['san-diego', 'topeka', 'dif2', 2.4] + ], + 'x': [ + ['seattle', 'new-york', 'dif1', 50.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'new-york', 'dif2', 150.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'chicago', 'dif1', 300.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'chicago', 'dif2', 400.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'new-york', 'dif1', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'new-york', 'dif2', 375.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'topeka', 'dif1', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'topeka', 'dif2', 375.0, 0.0, 0.0, float('inf'), 1.0] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['full_example']], + ['File2', self.FILE_PATHS['full_example_changed_data_and_variables']] + ] + } + self.check_gdx_file(symbols) + + def test_full_example_and_full_example_changed_data_and_variables_id_x(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_data_and_variables'], + self.FILE_PATHS['diff_file'], + 'ID=x' + ]) + self.check_output( + output, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'x': [ + ['seattle', 'new-york', 'dif1', 50.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'new-york', 'dif2', 150.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'chicago', 'dif1', 300.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'chicago', 'dif2', 400.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'new-york', 'dif1', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'new-york', 'dif2', 375.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'topeka', 'dif1', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'topeka', 'dif2', 375.0, 0.0, 0.0, float('inf'), 1.0] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['full_example']], + ['File2', self.FILE_PATHS['full_example_changed_data_and_variables']] + ] + } + self.check_gdx_file(symbols) + + output_space_separator = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_data_and_variables'], + self.FILE_PATHS['diff_file'], + 'ID', 'x' + ]) + self.check_output( + output_space_separator, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + self.check_gdx_file(symbols) + + def test_full_example_and_full_example_changed_data_and_variables_id_c_d(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_data_and_variables'], + self.FILE_PATHS['diff_file'], + 'ID=c', 'ID=d' + ]) + self.check_output( + output, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'c': [ + ['seattle', 'new-york', 'dif1', 0.225], + ['seattle', 'new-york', 'dif2', 0.315], + ['seattle', 'chicago', 'dif1', 0.153], + ['seattle', 'chicago', 'dif2', 0.24300000000000002], + ['seattle', 'topeka', 'dif1', 0.162], + ['seattle', 'topeka', 'dif2', 0.25199999999999995], + ['san-diego', 'new-york', 'dif1', 0.225], + ['san-diego', 'new-york', 'dif2', 0.315], + ['san-diego', 'chicago', 'dif1', 0.162], + ['san-diego', 'chicago', 'dif2', 0.25199999999999995], + ['san-diego', 'topeka', 'dif1', 0.12599999999999997], + ['san-diego', 'topeka', 'dif2', 0.216] + ], + 'd': [ + ['seattle', 'new-york', 'dif1', 2.5], + ['seattle', 'new-york', 'dif2', 3.5], + ['seattle', 'chicago', 'dif1', 1.7], + ['seattle', 'chicago', 'dif2', 2.7], + ['seattle', 'topeka', 'dif1', 1.8], + ['seattle', 'topeka', 'dif2', 2.8], + ['san-diego', 'new-york', 'dif1', 2.5], + ['san-diego', 'new-york', 'dif2', 3.5], + ['san-diego', 'chicago', 'dif1', 1.8], + ['san-diego', 'chicago', 'dif2', 2.8], + ['san-diego', 'topeka', 'dif1', 1.4], + ['san-diego', 'topeka', 'dif2', 2.4] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['full_example']], + ['File2', self.FILE_PATHS['full_example_changed_data_and_variables']] + ] + } + self.check_gdx_file(symbols) + + output_quotation_marks = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_data_and_variables'], + self.FILE_PATHS['diff_file'], + 'ID=\'c d\'' + ]) + self.check_output( + output_quotation_marks, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + self.check_gdx_file(symbols) + + def test_full_example_and_full_example_changed_data_and_variables_skip_id_x(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_data_and_variables'], + self.FILE_PATHS['diff_file'], + 'SkipID=x' + ]) + self.check_output( + output, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'c': [ + ['seattle', 'new-york', 'dif1', 0.225], + ['seattle', 'new-york', 'dif2', 0.315], + ['seattle', 'chicago', 'dif1', 0.153], + ['seattle', 'chicago', 'dif2', 0.24300000000000002], + ['seattle', 'topeka', 'dif1', 0.162], + ['seattle', 'topeka', 'dif2', 0.25199999999999995], + ['san-diego', 'new-york', 'dif1', 0.225], + ['san-diego', 'new-york', 'dif2', 0.315], + ['san-diego', 'chicago', 'dif1', 0.162], + ['san-diego', 'chicago', 'dif2', 0.25199999999999995], + ['san-diego', 'topeka', 'dif1', 0.12599999999999997], + ['san-diego', 'topeka', 'dif2', 0.216] + ], + 'd': [ + ['seattle', 'new-york', 'dif1', 2.5], + ['seattle', 'new-york', 'dif2', 3.5], + ['seattle', 'chicago', 'dif1', 1.7], + ['seattle', 'chicago', 'dif2', 2.7], + ['seattle', 'topeka', 'dif1', 1.8], + ['seattle', 'topeka', 'dif2', 2.8], + ['san-diego', 'new-york', 'dif1', 2.5], + ['san-diego', 'new-york', 'dif2', 3.5], + ['san-diego', 'chicago', 'dif1', 1.8], + ['san-diego', 'chicago', 'dif2', 2.8], + ['san-diego', 'topeka', 'dif1', 1.4], + ['san-diego', 'topeka', 'dif2', 2.4] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['full_example']], + ['File2', self.FILE_PATHS['full_example_changed_data_and_variables']] + ] + } + self.check_gdx_file(symbols) + + output_space_separator = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_data_and_variables'], + self.FILE_PATHS['diff_file'], + 'SkipID', 'x' + ]) + self.check_output( + output_space_separator, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + self.check_gdx_file(symbols) + + def test_full_example_and_full_example_changed_data_and_variables_skip_id_c_d(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_data_and_variables'], + self.FILE_PATHS['diff_file'], + 'SkipID=c', 'SkipID=d' + ]) + self.check_output( + output, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'x': [ + ['seattle', 'new-york', 'dif1', 50.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'new-york', 'dif2', 150.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'chicago', 'dif1', 300.0, 0.0, 0.0, float('inf'), 1.0], + ['seattle', 'chicago', 'dif2', 400.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'new-york', 'dif1', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'new-york', 'dif2', 375.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'topeka', 'dif1', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['san-diego', 'topeka', 'dif2', 375.0, 0.0, 0.0, float('inf'), 1.0] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['full_example']], + ['File2', self.FILE_PATHS['full_example_changed_data_and_variables']] + ] + } + self.check_gdx_file(symbols) + + output_quotation_marks = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_data_and_variables'], + self.FILE_PATHS['diff_file'], + 'SkipID=\'c d\'' + ]) + self.check_output( + output_quotation_marks, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + self.check_gdx_file(symbols) + + def test_full_example_and_full_example_changed_variables_differences_only(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['full_example'], + self.FILE_PATHS['full_example_changed_variables'], + self.FILE_PATHS['diff_file'], + 'DiffOnly' + ]) + self.check_output( + output, + return_code=1, + file_name='full_example_and_full_example_changed_variables.txt', + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'x': [ + ['seattle', 'new-york', 'Level', 'dif1', 50.0], + ['seattle', 'new-york', 'Level', 'dif2', 150.0], + ['seattle', 'chicago', 'Level', 'dif1', 300.0], + ['seattle', 'chicago', 'Level', 'dif2', 400.0], + ['san-diego', 'new-york', 'Level', 'dif1', 275.0], + ['san-diego', 'new-york', 'Level', 'dif2', 375.0], + ['san-diego', 'topeka', 'Level', 'dif1', 275.0], + ['san-diego', 'topeka', 'Level', 'dif2', 375.0] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['full_example']], + ['File2', self.FILE_PATHS['full_example_changed_variables']] + ] + } + self.check_gdx_file(symbols) + + def test_default_values_example_1_and_default_values_example_2(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['default_values_example_1'], + self.FILE_PATHS['default_values_example_2'], + self.FILE_PATHS['diff_file'] + ]) + self.check_output( + output, + return_code=0, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'FilesCompared': [ + ['File1', self.FILE_PATHS['default_values_example_1']], + ['File2', self.FILE_PATHS['default_values_example_2']] + ] + } + self.check_gdx_file(symbols) + + def test_default_values_example_1_and_default_values_example_2_compare_default_values(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['default_values_example_1'], + self.FILE_PATHS['default_values_example_2'], + self.FILE_PATHS['diff_file'], + 'CmpDefaults' + ]) + self.check_output( + output, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'v': [ + ['i0', 'ins1', 0.0, 0.0, float('-inf'), float('inf'), 1.0] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['default_values_example_1']], + ['File2', self.FILE_PATHS['default_values_example_2']] + ] + } + self.check_gdx_file(symbols) + + def test_domain_example_1_and_domain_example_2(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['domain_example_1'], + self.FILE_PATHS['domain_example_2'], + self.FILE_PATHS['diff_file'] + ]) + self.check_output( + output, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'FilesCompared': [ + ['File1', self.FILE_PATHS['domain_example_1']], + ['File2', self.FILE_PATHS['domain_example_2']] + ] + } + self.check_gdx_file(symbols) + + def test_domain_example_1_and_domain_example_2_compare_symbol_domains(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['domain_example_1'], + self.FILE_PATHS['domain_example_2'], + self.FILE_PATHS['diff_file'], + 'CmpDomains' + ]) + self.check_output( + output, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'FilesCompared': [ + ['File1', self.FILE_PATHS['domain_example_1']], + ['File2', self.FILE_PATHS['domain_example_2']] + ] + } + self.check_gdx_file(symbols) + + def test_order_example_1_and_order_example_2(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['order_example_1'], + self.FILE_PATHS['order_example_2'], + self.FILE_PATHS['diff_file'] + ]) + self.check_output( + output, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 't1': [ + ['1988', 'ins1', ''], + ['1990', 'ins1', ''], + ['1983', 'ins2', ''], + ['1985', 'ins2', ''] + ], + 't2': [ + ['1988', 'ins2', ''], + ['1989', 'ins2', ''], + ['1990', 'ins2', ''], + ['1991', 'ins2', ''], + ['1983', 'ins1', ''], + ['1984', 'ins1', ''], + ['1985', 'ins1', ''], + ['1986', 'ins1', ''] + ], + 't3': [ + ['1989', 'ins1', ''], + ['1991', 'ins1', ''], + ['1984', 'ins2', ''], + ['1986', 'ins2', ''] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['order_example_1']], + ['File2', self.FILE_PATHS['order_example_2']] + ] + } + self.check_gdx_file(symbols) + + def test_order_example_1_and_order_example_2_ignore_order(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['order_example_1'], + self.FILE_PATHS['order_example_2'], + self.FILE_PATHS['diff_file'], + 'IgnoreOrder' + ]) + self.check_output( + output, + return_code=1, + file_name='order_example_1_and_order_example_2.txt', + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 't1': [ + ['1988', 'ins1', ''], + ['1990', 'ins1', ''], + ['1983', 'ins2', ''], + ['1985', 'ins2', ''] + ], + 't2': [ + ['1988', 'ins2', ''], + ['1990', 'ins2', ''], + ['1983', 'ins1', ''], + ['1985', 'ins1', ''], + ['1989', 'ins2', ''], + ['1991', 'ins2', ''], + ['1984', 'ins1', ''], + ['1986', 'ins1', ''] + ], + 't3': [ + ['1989', 'ins1', ''], + ['1991', 'ins1', ''], + ['1984', 'ins2', ''], + ['1986', 'ins2', ''] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['order_example_1']], + ['File2', self.FILE_PATHS['order_example_2']] + ] + } + self.check_gdx_file(symbols) + + def test_description_example_1_and_description_example_2_compare_associated_texts_y(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['description_example_1'], + self.FILE_PATHS['description_example_2'], + self.FILE_PATHS['diff_file'], + 'SetDesc=Y' + ]) + self.check_output( + output, + return_code=1, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'i': [ + ['seattle', 'dif1', 'text 1'], + ['seattle', 'dif2', 'text 3'], + ['san-diego', 'dif1', 'text 2'], + ['san-diego', 'dif2', 'text 4'] + ], + 'FilesCompared': [ + ['File1', self.FILE_PATHS['description_example_1']], + ['File2', self.FILE_PATHS['description_example_2']] + ] + } + self.check_gdx_file(symbols) + + def test_description_example_1_and_description_example_2_compare_associated_texts_n(self) -> None: + output = self.run_gdxdiff([ + self.FILE_PATHS['description_example_1'], + self.FILE_PATHS['description_example_2'], + self.FILE_PATHS['diff_file'], + 'SetDesc=N' + ]) + self.check_output( + output, + return_code=0, + first_offset=3, + second_offset=3, + first_delete=[-3], + second_delete=[-3] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'FilesCompared': [ + ['File1', self.FILE_PATHS['description_example_1']], + ['File2', self.FILE_PATHS['description_example_2']] + ] + } + self.check_gdx_file(symbols) + + # TODO: Test the MatrixFile option as well diff --git a/src/tools/tests/gdxdump.py b/src/tools/tests/gdxdump.py new file mode 100644 index 0000000..f90d439 --- /dev/null +++ b/src/tools/tests/gdxdump.py @@ -0,0 +1,479 @@ +import unittest +import os +import platform +import subprocess +import tempfile +import inspect + +from examples.small_example import create_small_example +from examples.full_example import create_full_example +from examples.element_text_example import create_element_text_example +from examples.special_values_example import create_special_values_example +from examples.label_example import create_label_example + + +class TestGdxDump(unittest.TestCase): + TESTS_DIRECTORY_PATH = os.path.dirname(os.path.abspath(__file__)) + DIRECTORY_PATHS = { + 'examples': os.path.join(TESTS_DIRECTORY_PATH, 'examples'), + 'output': os.path.join(TESTS_DIRECTORY_PATH, 'output', 'gdxdump') + } + FILE_NAMES = [ + 'small_example', + 'full_example', + 'element_text_example', + 'special_values_example', + 'label_example' + ] + FILE_PATHS: dict[str, str] + + @classmethod + def setUpClass(cls) -> None: + cls.FILE_PATHS = { + file_name: os.path.join( + cls.DIRECTORY_PATHS['examples'], + f'{file_name}.gdx' + ) for file_name in cls.FILE_NAMES + } + + create_small_example(cls.FILE_PATHS['small_example']) + create_full_example(cls.FILE_PATHS['full_example']) + create_element_text_example(cls.FILE_PATHS['element_text_example']) + create_special_values_example(cls.FILE_PATHS['special_values_example']) + create_label_example(cls.FILE_PATHS['label_example']) + + @classmethod + def tearDownClass(cls) -> None: + for file_path in cls.FILE_PATHS.values(): + os.remove(file_path) + + @classmethod + def run_gdxdump(cls, command: list[str]) -> subprocess.CompletedProcess[str]: + EXECUTABLE_NAME = 'gdxdump' + if platform.system() == 'Windows': + EXECUTABLE_PATH = ['Release', f'{EXECUTABLE_NAME}.exe'] + else: + EXECUTABLE_PATH = ['build', EXECUTABLE_NAME] + return subprocess.run( + [os.path.join(cls.TESTS_DIRECTORY_PATH, '..', '..', '..', *EXECUTABLE_PATH), *command], + capture_output=True, + text=True + ) + + def check_output( + self, + output: subprocess.CompletedProcess[str], + return_code=0, + file_name: str | None = None, + first_offset: int | None = None, + first_negative_offset: int | None = None, + second_offset: int | None = None, + second_negative_offset: int | None = None, + first_delete: list[int] = [], + second_delete: list[int] = [] + ) -> None: + self.assertEqual(output.returncode, return_code) + first = output.stdout.split('\n')[first_offset:first_negative_offset] + for i in first_delete: + del first[i] + if file_name is None: + file_name = f'{inspect.stack()[1].function.removeprefix('test_')}.txt' + with open(os.path.join(self.DIRECTORY_PATHS['output'], file_name), 'r') as file: + second = file.read().split('\n')[second_offset:second_negative_offset] + for i in second_delete: + del second[i] + self.assertEqual(first, second) + self.assertEqual(output.stderr, '') + + def test_empty_command(self) -> None: + output = self.run_gdxdump([]) + self.check_output( + output, + return_code=1, + file_name='usage.txt', + first_delete=[1], + second_delete=[1] + ) + + def test_small_example(self) -> None: + output = self.run_gdxdump([self.FILE_PATHS['small_example']]) + self.check_output(output) + + def test_full_example(self) -> None: + output = self.run_gdxdump([self.FILE_PATHS['full_example']]) + self.check_output(output) + + def test_full_example_output(self) -> None: + with tempfile.NamedTemporaryFile(suffix='.txt') as temporary_file: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + f'Output={temporary_file.name}' + ]) + self.assertEqual(output.returncode, 0) + with open(temporary_file.name, 'r') as file: + first = file.read().split('\n') + with open(os.path.join(self.DIRECTORY_PATHS['output'], 'full_example.txt'), 'r') as file: + second = file.read().split('\n') + self.assertEqual(first, second) + self.assertEqual(output.stdout, '') + self.assertEqual(output.stderr, '') + + def test_full_example_version(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + '-Version' + ]) + self.check_output( + output, + first_offset=1, + second_offset=1 + ) + + output_short = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + '-V' + ]) + self.check_output( + output_short, + first_offset=1, + second_offset=1 + ) + + def test_full_example_symbol(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Symb=i' + ]) + self.check_output(output) + + output_lowercase = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'symb=i' + ]) + self.check_output(output_lowercase) + + output_space_separator = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Symb', 'i' + ]) + self.check_output(output_space_separator) + + def test_full_example_symbol_missing_identifier(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Symb' + ]) + self.check_output( + output, + return_code=1, + first_delete=[2], + second_delete=[2] + ) + + def test_full_example_symbol_not_found(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Symb=e' + ]) + self.check_output( + output, + return_code=6 + ) + + def test_full_example_uel_table(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'UelTable=e' + ]) + self.check_output(output) + + def test_full_example_uel_table_missing_identifier(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'UelTable' + ]) + self.check_output( + output, + return_code=1, + first_delete=[2], + second_delete=[2] + ) + + def test_full_example_delimiter_period(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Delim=period' + ]) + self.check_output(output) + + def test_full_example_delimiter_comma(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Delim=comma' + ]) + self.check_output(output) + + def test_full_example_delimiter_tab(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Delim=tab' + ]) + self.check_output(output) + + def test_full_example_delimiter_blank(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Delim=blank' + ]) + self.check_output(output) + + def test_full_example_delimiter_semicolon(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Delim=semicolon' + ]) + self.check_output(output) + + def test_full_example_delimiter_missing(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Delim' + ]) + self.check_output( + output, + return_code=1, + first_delete=[2], + second_delete=[2] + ) + + def test_full_example_decimal_separator_period(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'DecimalSep=period' + ]) + self.check_output(output) + + def test_full_example_decimal_separator_comma(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'DecimalSep=comma' + ]) + self.check_output(output) + + def test_full_example_format_normal(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Format=normal' + ]) + self.check_output(output) + + def test_full_example_format_gamsbas(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Format=gamsbas' + ]) + self.check_output(output) + + def test_full_example_format_csv(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Format=csv' + ]) + self.check_output( + output, + return_code=1 + ) + + def test_full_example_symbol_format_csv(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Symb=a', + 'Format=csv' + ]) + self.check_output(output) + + def test_full_example_symbol_format_csv_header(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Symb=a', + 'Format=csv', + 'Header=Test' + ]) + self.check_output(output) + + def test_full_example_symbol_format_csv_header_missing_identifier(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Symb=a', + 'Format=csv', + 'Header' + ]) + self.check_output(output) + + def test_full_example_symbol_format_csv_no_header(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Symb=a', + 'Format=csv', + 'NoHeader' + ]) + self.check_output(output) + + def test_full_example_numerical_format_normal(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'dFormat=normal' + ]) + self.check_output(output) + + def test_full_example_numerical_format_hexponential(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'dFormat=hexponential' + ]) + self.check_output(output) + + def test_full_example_numerical_format_hexbytes(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'dFormat=hexBytes' + ]) + self.check_output(output) + + output_lowercase = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'dformat=hexbytes' + ]) + self.check_output(output_lowercase) + + def test_full_example_no_data(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'NoData' + ]) + self.check_output( + output, + first_offset=2, + second_offset=2 + ) + + def test_full_example_symbol_format_csv_all_fields(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Symb=demand', + 'Format=csv', + 'CSVAllFields' + ]) + self.check_output(output) + + def test_element_text_example_symbol_format_csv_set_text(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['element_text_example'], + 'Symb=j', + 'Format=csv', + 'CSVSetText' + ]) + self.check_output(output) + + def test_full_example_symbols(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Symbols' + ]) + self.check_output(output) + + def test_full_example_symbols_as_set(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'SymbolsAsSet' + ]) + self.check_output(output) + + def test_full_example_symbols_as_set_domain_information(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'SymbolsAsSetDI' + ]) + self.check_output(output) + + def test_full_example_domain_information(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'DomainInfo' + ]) + self.check_output(output) + + def test_element_text_example_set_text(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['element_text_example'], + 'SetText' + ]) + self.check_output(output) + + def test_full_example_symbol_format_csv_cdim(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'Symb=x', + 'Format=csv', + 'cDim=Y' + ]) + self.check_output(output) + + def test_full_example_filter_default_values(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['full_example'], + 'FilterDef=N' + ]) + self.check_output(output) + + def test_special_values_example_filter_default_values_out_epsilon(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['special_values_example'], + 'FilterDef=N', + 'EpsOut=Test' + ]) + self.check_output(output) + + def test_special_values_example_filter_default_values_out_not_available(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['special_values_example'], + 'FilterDef=N', + 'NaOut=Test' + ]) + self.check_output(output) + + def test_special_values_example_filter_default_values_out_positive_infinity(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['special_values_example'], + 'FilterDef=N', + 'PinfOut=Test' + ]) + self.check_output(output) + + def test_special_values_example_filter_default_values_out_negative_infinity(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['special_values_example'], + 'FilterDef=N', + 'MinfOut=Test' + ]) + self.check_output(output) + + def test_special_values_example_filter_default_values_out_undefined(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['special_values_example'], + 'FilterDef=N', + 'UndfOut=Test' + ]) + self.check_output(output) + + def test_special_values_example_filter_default_values_out_zero(self) -> None: + output = self.run_gdxdump([ + self.FILE_PATHS['special_values_example'], + 'FilterDef=N', + 'ZeroOut=Test' + ]) + self.check_output(output) + + def test_label_example(self) -> None: + output = self.run_gdxdump([self.FILE_PATHS['label_example']]) + self.check_output(output) diff --git a/src/tools/tests/gdxmerge.py b/src/tools/tests/gdxmerge.py new file mode 100644 index 0000000..6e240ed --- /dev/null +++ b/src/tools/tests/gdxmerge.py @@ -0,0 +1,792 @@ +import unittest +import os +import platform +import subprocess +import tempfile +import inspect +import gams.transfer as gt + +from examples.small_example import create_small_example +from examples.full_example import create_full_example +from examples.small_example_changed_data import create_small_example_changed_data + + +class TestGdxMerge(unittest.TestCase): + TESTS_DIRECTORY_PATH = os.path.dirname(os.path.abspath(__file__)) + DIRECTORY_PATHS = { + 'examples': os.path.join(TESTS_DIRECTORY_PATH, 'examples'), + 'output': os.path.join(TESTS_DIRECTORY_PATH, 'output', 'gdxmerge') + } + FILE_NAMES = [ + 'small_example', + 'full_example', + 'small_example_changed_data', + 'merge_file' + ] + FILE_PATHS: dict[str, str] + + @classmethod + def setUpClass(cls) -> None: + cls.FILE_PATHS = { + file_name: os.path.join( + cls.DIRECTORY_PATHS['examples'], + f'{file_name}.gdx' + ) for file_name in cls.FILE_NAMES + } + + create_small_example(cls.FILE_PATHS['small_example']) + create_full_example(cls.FILE_PATHS['full_example']) + create_small_example_changed_data(cls.FILE_PATHS['small_example_changed_data']) + + @classmethod + def tearDownClass(cls) -> None: + for file_path in cls.FILE_PATHS.values(): + os.remove(file_path) + + @classmethod + def run_gdxmerge(cls, command: list[str]) -> subprocess.CompletedProcess[str]: + EXECUTABLE_NAME = 'gdxmerge' + if platform.system() == 'Windows': + EXECUTABLE_PATH = ['Release', f'{EXECUTABLE_NAME}.exe'] + else: + EXECUTABLE_PATH = ['build', EXECUTABLE_NAME] + return subprocess.run( + [os.path.join(cls.TESTS_DIRECTORY_PATH, '..', '..', '..', *EXECUTABLE_PATH), *command], + capture_output=True, + text=True + ) + + def check_output( + self, + output: subprocess.CompletedProcess[str], + return_code=0, + file_name: str | None = None, + first_offset: int | None = None, + first_negative_offset: int | None = None, + second_offset: int | None = None, + second_negative_offset: int | None = None, + first_delete: list[int] = [], + second_delete: list[int] = [] + ) -> None: + self.assertEqual(output.returncode, return_code) + first = output.stdout.split('\n')[first_offset:first_negative_offset] + for i in first_delete: + del first[i] + if file_name is None: + file_name = f'{inspect.stack()[1].function.removeprefix('test_')}.txt' + with open(os.path.join(self.DIRECTORY_PATHS['output'], file_name), 'r') as file: + second = file.read().split('\n')[second_offset:second_negative_offset] + for i in second_delete: + del second[i] + self.assertEqual(first, second) + self.assertEqual(output.stderr, '') + + def check_gdx_file_symbols( + self, + container: gt.Container, + symbol_names: list[str] + ) -> None: + for symbol_name in symbol_names: + with self.subTest(symbol_name=symbol_name): + self.assertIn(symbol_name, container) + self.assertEqual(len(container), len(symbol_names) + 1) + + def check_gdx_file_values( + self, + container: gt.Container, + symbol_name: str, + expected_values: list[list[str | float]] + ) -> None: + self.assertIn(symbol_name, container) + symbol: gt.Parameter = container[symbol_name] # type: ignore + values = symbol.records.values.tolist() + self.assertEqual(values, expected_values) + + def check_gdx_file( + self, + symbols: dict[str, list[list[str | float]]], + file_names: list[str] + ) -> None: + container = gt.Container(load_from=self.FILE_PATHS['merge_file']) + self.check_gdx_file_symbols(container, list(symbols.keys())) + for symbol_name in symbols: + self.check_gdx_file_values(container, symbol_name, symbols[symbol_name]) + + symbol: gt.Parameter = container['Merged_set_1'] # type: ignore + first = symbol.records.values.tolist() + second: list[list[str]] = [] + for i in range(len(file_names)): + second.append([file_names[i], f'{r'\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .+[/\\]examples[/\\]'}{file_names[i]}.gdx']) + self.assertEqual(len(first), len(file_names)) + for item in first: + self.assertEqual(len(item), 2) + for i in range(len(file_names)): + self.assertEqual(first[i][0], second[i][0]) + self.assertRegex(first[i][1], second[i][1]) + + def test_empty_command(self) -> None: + output = self.run_gdxmerge([]) + self.check_output( + output, + return_code=0, + file_name='usage.txt', + first_delete=[1], + second_delete=[1] + ) + + def test_small_example_and_full_example(self) -> None: + output = self.run_gdxmerge([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['full_example'], + f'Output={self.FILE_PATHS['merge_file']}' + ]) + self.check_output( + output, + return_code=0, + first_offset=3, + second_offset=3 + ) + + symbols: dict[str, list[list[str | float]]] = { + 'i': [ + ['small_example', 'seattle', ''], + ['small_example', 'san-diego', ''], + ['full_example', 'seattle', ''], + ['full_example', 'san-diego', ''] + ], + 'j': [ + ['small_example', 'new-york', ''], + ['small_example', 'chicago', ''], + ['small_example', 'topeka', ''], + ['full_example', 'new-york', ''], + ['full_example', 'chicago', ''], + ['full_example', 'topeka', ''] + ], + 'd': [ + ['small_example', 'seattle', 'new-york', 2.5], + ['small_example', 'seattle', 'chicago', 1.7], + ['small_example', 'seattle', 'topeka', 1.8], + ['small_example', 'san-diego', 'new-york', 2.5], + ['small_example', 'san-diego', 'chicago', 1.8], + ['small_example', 'san-diego', 'topeka', 1.4], + ['full_example', 'seattle', 'new-york', 2.5], + ['full_example', 'seattle', 'chicago', 1.7], + ['full_example', 'seattle', 'topeka', 1.8], + ['full_example', 'san-diego', 'new-york', 2.5], + ['full_example', 'san-diego', 'chicago', 1.8], + ['full_example', 'san-diego', 'topeka', 1.4] + ], + 'a': [ + ['full_example', 'seattle', 350.0], + ['full_example', 'san-diego', 600.0] + ], + 'b': [ + ['full_example', 'new-york', 325.0], + ['full_example', 'chicago', 300.0], + ['full_example', 'topeka', 275.0] + ], + 'f': [ + ['full_example', 90.0] + ], + 'c': [ + ['full_example', 'seattle', 'new-york', 0.225], + ['full_example', 'seattle', 'chicago', 0.153], + ['full_example', 'seattle', 'topeka', 0.162], + ['full_example', 'san-diego', 'new-york', 0.225], + ['full_example', 'san-diego', 'chicago', 0.162], + ['full_example', 'san-diego', 'topeka', 0.12599999999999997] + ], + 'x': [ + ['full_example', 'seattle', 'new-york', 50.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'seattle', 'chicago', 300.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'seattle', 'topeka', 0.0, 0.036, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'new-york', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'chicago', 0.0, 0.009, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'topeka', 275.0, 0.0, 0.0, float('inf'), 1.0] + ], + 'z': [ + ['full_example', 153.675, 0.0, float('-inf'), float('inf'), 1.0] + ], + 'cost': [ + ['full_example', 0.0, 1.0, 0.0, 0.0, 1.0] + ], + 'supply': [ + ['full_example', 'seattle', 350.0, 0.0, float('-inf'), 350.0, 1.0], + ['full_example', 'san-diego', 550.0, 0.0, float('-inf'), 600.0, 1.0] + ], + 'demand': [ + ['full_example', 'new-york', 325.0, 0.225, 325.0, float('inf'), 1.0], + ['full_example', 'chicago', 300.0, 0.153, 300.0, float('inf'), 1.0], + ['full_example', 'topeka', 275.0, 0.126, 275.0, float('inf'), 1.0] + ] + } + self.check_gdx_file(symbols, ['small_example', 'full_example']) + + def test_small_example_and_full_example_id_i(self) -> None: + output = self.run_gdxmerge([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['full_example'], + f'Output={self.FILE_PATHS['merge_file']}', + 'Id=i' + ]) + self.check_output( + output, + return_code=0, + first_delete=[1, 1, 1], + second_delete=[1, 1, 1] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'i': [ + ['small_example', 'seattle', ''], + ['small_example', 'san-diego', ''], + ['full_example', 'seattle', ''], + ['full_example', 'san-diego', ''] + ] + } + self.check_gdx_file(symbols, ['small_example', 'full_example']) + + def test_small_example_and_full_example_id_i_j(self) -> None: + output = self.run_gdxmerge([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['full_example'], + f'Output={self.FILE_PATHS['merge_file']}', + 'Id=i', 'Id=j' + ]) + self.check_output( + output, + return_code=0, + first_delete=[2, 2, 2], + second_delete=[2, 2, 2] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'i': [ + ['small_example', 'seattle', ''], + ['small_example', 'san-diego', ''], + ['full_example', 'seattle', ''], + ['full_example', 'san-diego', ''] + ], + 'j': [ + ['small_example', 'new-york', ''], + ['small_example', 'chicago', ''], + ['small_example', 'topeka', ''], + ['full_example', 'new-york', ''], + ['full_example', 'chicago', ''], + ['full_example', 'topeka', ''] + ] + } + self.check_gdx_file(symbols, ['small_example', 'full_example']) + + output_quotation_marks = self.run_gdxmerge([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['full_example'], + f'Output={self.FILE_PATHS['merge_file']}', + 'Id=\'i j\'' + ]) + self.check_output( + output_quotation_marks, + return_code=0, + first_delete=[2, 2, 2], + second_delete=[2, 2, 2] + ) + self.check_gdx_file(symbols, ['small_example', 'full_example']) + + output_comma_separator = self.run_gdxmerge([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['full_example'], + f'Output={self.FILE_PATHS['merge_file']}', + 'Id=i,j' + ]) + self.check_output( + output_comma_separator, + return_code=0, + first_delete=[2, 2, 2], + second_delete=[2, 2, 2] + ) + self.check_gdx_file(symbols, ['small_example', 'full_example']) + + def test_small_example_and_full_example_exclude_i(self) -> None: + output = self.run_gdxmerge([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['full_example'], + f'Output={self.FILE_PATHS['merge_file']}', + 'Exclude=i' + ]) + self.check_output( + output, + return_code=0, + first_delete=[1, 1, 1], + second_delete=[1, 1, 1] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'j': [ + ['small_example', 'new-york', ''], + ['small_example', 'chicago', ''], + ['small_example', 'topeka', ''], + ['full_example', 'new-york', ''], + ['full_example', 'chicago', ''], + ['full_example', 'topeka', ''] + ], + 'd': [ + ['small_example', 'seattle', 'new-york', 2.5], + ['small_example', 'seattle', 'chicago', 1.7], + ['small_example', 'seattle', 'topeka', 1.8], + ['small_example', 'san-diego', 'new-york', 2.5], + ['small_example', 'san-diego', 'chicago', 1.8], + ['small_example', 'san-diego', 'topeka', 1.4], + ['full_example', 'seattle', 'new-york', 2.5], + ['full_example', 'seattle', 'chicago', 1.7], + ['full_example', 'seattle', 'topeka', 1.8], + ['full_example', 'san-diego', 'new-york', 2.5], + ['full_example', 'san-diego', 'chicago', 1.8], + ['full_example', 'san-diego', 'topeka', 1.4] + ], + 'a': [ + ['full_example', 'seattle', 350.0], + ['full_example', 'san-diego', 600.0] + ], + 'b': [ + ['full_example', 'new-york', 325.0], + ['full_example', 'chicago', 300.0], + ['full_example', 'topeka', 275.0] + ], + 'f': [ + ['full_example', 90.0] + ], + 'c': [ + ['full_example', 'seattle', 'new-york', 0.225], + ['full_example', 'seattle', 'chicago', 0.153], + ['full_example', 'seattle', 'topeka', 0.162], + ['full_example', 'san-diego', 'new-york', 0.225], + ['full_example', 'san-diego', 'chicago', 0.162], + ['full_example', 'san-diego', 'topeka', 0.12599999999999997] + ], + 'x': [ + ['full_example', 'seattle', 'new-york', 50.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'seattle', 'chicago', 300.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'seattle', 'topeka', 0.0, 0.036, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'new-york', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'chicago', 0.0, 0.009, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'topeka', 275.0, 0.0, 0.0, float('inf'), 1.0] + ], + 'z': [ + ['full_example', 153.675, 0.0, float('-inf'), float('inf'), 1.0] + ], + 'cost': [ + ['full_example', 0.0, 1.0, 0.0, 0.0, 1.0] + ], + 'supply': [ + ['full_example', 'seattle', 350.0, 0.0, float('-inf'), 350.0, 1.0], + ['full_example', 'san-diego', 550.0, 0.0, float('-inf'), 600.0, 1.0] + ], + 'demand': [ + ['full_example', 'new-york', 325.0, 0.225, 325.0, float('inf'), 1.0], + ['full_example', 'chicago', 300.0, 0.153, 300.0, float('inf'), 1.0], + ['full_example', 'topeka', 275.0, 0.126, 275.0, float('inf'), 1.0] + ] + } + self.check_gdx_file(symbols, ['small_example', 'full_example']) + + def test_small_example_and_full_example_exclude_i_j(self) -> None: + output = self.run_gdxmerge([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['full_example'], + f'Output={self.FILE_PATHS['merge_file']}', + 'Exclude=i', 'Exclude=j' + ]) + self.check_output( + output, + return_code=0, + first_delete=[2, 2, 2], + second_delete=[2, 2, 2] + ) + + symbols: dict[str, list[list[str | float]]] = { + 'd': [ + ['small_example', 'seattle', 'new-york', 2.5], + ['small_example', 'seattle', 'chicago', 1.7], + ['small_example', 'seattle', 'topeka', 1.8], + ['small_example', 'san-diego', 'new-york', 2.5], + ['small_example', 'san-diego', 'chicago', 1.8], + ['small_example', 'san-diego', 'topeka', 1.4], + ['full_example', 'seattle', 'new-york', 2.5], + ['full_example', 'seattle', 'chicago', 1.7], + ['full_example', 'seattle', 'topeka', 1.8], + ['full_example', 'san-diego', 'new-york', 2.5], + ['full_example', 'san-diego', 'chicago', 1.8], + ['full_example', 'san-diego', 'topeka', 1.4] + ], + 'a': [ + ['full_example', 'seattle', 350.0], + ['full_example', 'san-diego', 600.0] + ], + 'b': [ + ['full_example', 'new-york', 325.0], + ['full_example', 'chicago', 300.0], + ['full_example', 'topeka', 275.0] + ], + 'f': [ + ['full_example', 90.0] + ], + 'c': [ + ['full_example', 'seattle', 'new-york', 0.225], + ['full_example', 'seattle', 'chicago', 0.153], + ['full_example', 'seattle', 'topeka', 0.162], + ['full_example', 'san-diego', 'new-york', 0.225], + ['full_example', 'san-diego', 'chicago', 0.162], + ['full_example', 'san-diego', 'topeka', 0.12599999999999997] + ], + 'x': [ + ['full_example', 'seattle', 'new-york', 50.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'seattle', 'chicago', 300.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'seattle', 'topeka', 0.0, 0.036, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'new-york', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'chicago', 0.0, 0.009, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'topeka', 275.0, 0.0, 0.0, float('inf'), 1.0] + ], + 'z': [ + ['full_example', 153.675, 0.0, float('-inf'), float('inf'), 1.0] + ], + 'cost': [ + ['full_example', 0.0, 1.0, 0.0, 0.0, 1.0] + ], + 'supply': [ + ['full_example', 'seattle', 350.0, 0.0, float('-inf'), 350.0, 1.0], + ['full_example', 'san-diego', 550.0, 0.0, float('-inf'), 600.0, 1.0] + ], + 'demand': [ + ['full_example', 'new-york', 325.0, 0.225, 325.0, float('inf'), 1.0], + ['full_example', 'chicago', 300.0, 0.153, 300.0, float('inf'), 1.0], + ['full_example', 'topeka', 275.0, 0.126, 275.0, float('inf'), 1.0] + ] + } + self.check_gdx_file(symbols, ['small_example', 'full_example']) + + output_quotation_marks = self.run_gdxmerge([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['full_example'], + f'Output={self.FILE_PATHS['merge_file']}', + 'Exclude=\'i j\'' + ]) + self.check_output( + output_quotation_marks, + return_code=0, + first_delete=[2, 2, 2], + second_delete=[2, 2, 2] + ) + self.check_gdx_file(symbols, ['small_example', 'full_example']) + + output_comma_separator = self.run_gdxmerge([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['full_example'], + f'Output={self.FILE_PATHS['merge_file']}', + 'Exclude=i,j' + ]) + self.check_output( + output_comma_separator, + return_code=0, + first_delete=[2, 2, 2], + second_delete=[2, 2, 2] + ) + self.check_gdx_file(symbols, ['small_example', 'full_example']) + + def test_small_example_and_full_example_big_symbols(self) -> None: + output = self.run_gdxmerge([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['full_example'], + f'Output={self.FILE_PATHS['merge_file']}', + 'Big=10' + ]) + self.check_output( + output, + return_code=0, + first_offset=5, + second_offset=5 + ) + + symbols: dict[str, list[list[str | float]]] = { + 'i': [ + ['small_example', 'seattle', ''], + ['small_example', 'san-diego', ''], + ['full_example', 'seattle', ''], + ['full_example', 'san-diego', ''] + ], + 'j': [ + ['small_example', 'new-york', ''], + ['small_example', 'chicago', ''], + ['small_example', 'topeka', ''], + ['full_example', 'new-york', ''], + ['full_example', 'chicago', ''], + ['full_example', 'topeka', ''] + ], + 'd': [ + ['small_example', 'seattle', 'new-york', 2.5], + ['small_example', 'seattle', 'chicago', 1.7], + ['small_example', 'seattle', 'topeka', 1.8], + ['small_example', 'san-diego', 'new-york', 2.5], + ['small_example', 'san-diego', 'chicago', 1.8], + ['small_example', 'san-diego', 'topeka', 1.4], + ['full_example', 'seattle', 'new-york', 2.5], + ['full_example', 'seattle', 'chicago', 1.7], + ['full_example', 'seattle', 'topeka', 1.8], + ['full_example', 'san-diego', 'new-york', 2.5], + ['full_example', 'san-diego', 'chicago', 1.8], + ['full_example', 'san-diego', 'topeka', 1.4] + ], + 'a': [ + ['full_example', 'seattle', 350.0], + ['full_example', 'san-diego', 600.0] + ], + 'b': [ + ['full_example', 'new-york', 325.0], + ['full_example', 'chicago', 300.0], + ['full_example', 'topeka', 275.0] + ], + 'f': [ + ['full_example', 90.0] + ], + 'c': [ + ['full_example', 'seattle', 'new-york', 0.225], + ['full_example', 'seattle', 'chicago', 0.153], + ['full_example', 'seattle', 'topeka', 0.162], + ['full_example', 'san-diego', 'new-york', 0.225], + ['full_example', 'san-diego', 'chicago', 0.162], + ['full_example', 'san-diego', 'topeka', 0.12599999999999997] + ], + 'x': [ + ['full_example', 'seattle', 'new-york', 50.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'seattle', 'chicago', 300.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'seattle', 'topeka', 0.0, 0.036, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'new-york', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'chicago', 0.0, 0.009, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'topeka', 275.0, 0.0, 0.0, float('inf'), 1.0] + ], + 'z': [ + ['full_example', 153.675, 0.0, float('-inf'), float('inf'), 1.0] + ], + 'cost': [ + ['full_example', 0.0, 1.0, 0.0, 0.0, 1.0] + ], + 'supply': [ + ['full_example', 'seattle', 350.0, 0.0, float('-inf'), 350.0, 1.0], + ['full_example', 'san-diego', 550.0, 0.0, float('-inf'), 600.0, 1.0] + ], + 'demand': [ + ['full_example', 'new-york', 325.0, 0.225, 325.0, float('inf'), 1.0], + ['full_example', 'chicago', 300.0, 0.153, 300.0, float('inf'), 1.0], + ['full_example', 'topeka', 275.0, 0.126, 275.0, float('inf'), 1.0] + ] + } + self.check_gdx_file(symbols, ['small_example', 'full_example']) + + def test_small_example_and_full_example_strict_mode(self) -> None: + with tempfile.NamedTemporaryFile(suffix='.gdx') as temporary_file: + output = self.run_gdxmerge([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['full_example'], + f'Output={temporary_file.name}', + 'Strict=true' + ]) + self.assertEqual(output.returncode, 1) + self.assertEqual( + output.stdout, + f'*** Error : Output file "{temporary_file.name}" already exists (strict mode)\n' + ) + self.assertEqual(output.stderr, '') + + def test_small_example_and_small_example_changed_data_and_full_example(self) -> None: + output = self.run_gdxmerge([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['small_example_changed_data'], + self.FILE_PATHS['full_example'], + f'Output={self.FILE_PATHS['merge_file']}' + ]) + self.check_output( + output, + return_code=0, + first_offset=4, + second_offset=4 + ) + + symbols: dict[str, list[list[str | float]]] = { + 'i': [ + ['small_example', 'seattle', ''], + ['small_example', 'san-diego', ''], + ['small_example_changed_data', 'seattle', ''], + ['small_example_changed_data', 'san-diego', ''], + ['full_example', 'seattle', ''], + ['full_example', 'san-diego', ''] + ], + 'j': [ + ['small_example', 'new-york', ''], + ['small_example', 'chicago', ''], + ['small_example', 'topeka', ''], + ['small_example_changed_data', 'new-york', ''], + ['small_example_changed_data', 'chicago', ''], + ['small_example_changed_data', 'topeka', ''], + ['full_example', 'new-york', ''], + ['full_example', 'chicago', ''], + ['full_example', 'topeka', ''] + ], + 'd': [ + ['small_example', 'seattle', 'new-york', 2.5], + ['small_example', 'seattle', 'chicago', 1.7], + ['small_example', 'seattle', 'topeka', 1.8], + ['small_example', 'san-diego', 'new-york', 2.5], + ['small_example', 'san-diego', 'chicago', 1.8], + ['small_example', 'san-diego', 'topeka', 1.4], + ['small_example_changed_data', 'seattle', 'new-york', 3.5], + ['small_example_changed_data', 'seattle', 'chicago', 2.7], + ['small_example_changed_data', 'seattle', 'topeka', 2.8], + ['small_example_changed_data', 'san-diego', 'new-york', 3.5], + ['small_example_changed_data', 'san-diego', 'chicago', 2.8], + ['small_example_changed_data', 'san-diego', 'topeka', 2.4], + ['full_example', 'seattle', 'new-york', 2.5], + ['full_example', 'seattle', 'chicago', 1.7], + ['full_example', 'seattle', 'topeka', 1.8], + ['full_example', 'san-diego', 'new-york', 2.5], + ['full_example', 'san-diego', 'chicago', 1.8], + ['full_example', 'san-diego', 'topeka', 1.4] + ], + 'a': [ + ['full_example', 'seattle', 350.0], + ['full_example', 'san-diego', 600.0] + ], + 'b': [ + ['full_example', 'new-york', 325.0], + ['full_example', 'chicago', 300.0], + ['full_example', 'topeka', 275.0] + ], + 'f': [ + ['full_example', 90.0] + ], + 'c': [ + ['full_example', 'seattle', 'new-york', 0.225], + ['full_example', 'seattle', 'chicago', 0.153], + ['full_example', 'seattle', 'topeka', 0.162], + ['full_example', 'san-diego', 'new-york', 0.225], + ['full_example', 'san-diego', 'chicago', 0.162], + ['full_example', 'san-diego', 'topeka', 0.12599999999999997] + ], + 'x': [ + ['full_example', 'seattle', 'new-york', 50.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'seattle', 'chicago', 300.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'seattle', 'topeka', 0.0, 0.036, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'new-york', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'chicago', 0.0, 0.009, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'topeka', 275.0, 0.0, 0.0, float('inf'), 1.0] + ], + 'z': [ + ['full_example', 153.675, 0.0, float('-inf'), float('inf'), 1.0] + ], + 'cost': [ + ['full_example', 0.0, 1.0, 0.0, 0.0, 1.0] + ], + 'supply': [ + ['full_example', 'seattle', 350.0, 0.0, float('-inf'), 350.0, 1.0], + ['full_example', 'san-diego', 550.0, 0.0, float('-inf'), 600.0, 1.0] + ], + 'demand': [ + ['full_example', 'new-york', 325.0, 0.225, 325.0, float('inf'), 1.0], + ['full_example', 'chicago', 300.0, 0.153, 300.0, float('inf'), 1.0], + ['full_example', 'topeka', 275.0, 0.126, 275.0, float('inf'), 1.0] + ] + } + self.check_gdx_file(symbols, ['small_example', 'small_example_changed_data', 'full_example']) + + def test_commands_from_file(self) -> None: + for separator in [' ', '\t', '\n']: + with tempfile.NamedTemporaryFile(suffix='.txt') as temporary_file: + temporary_file.write( + str.encode(separator.join([ + self.FILE_PATHS['small_example'], + self.FILE_PATHS['full_example'], + f'Output={self.FILE_PATHS['merge_file']}' + ])) + ) + temporary_file.seek(0) + output = self.run_gdxmerge([f'@{temporary_file.name}']) + self.check_output( + output, + return_code=0, + file_name='small_example_and_full_example.txt', + first_offset=3, + second_offset=3 + ) + + symbols: dict[str, list[list[str | float]]] = { + 'i': [ + ['small_example', 'seattle', ''], + ['small_example', 'san-diego', ''], + ['full_example', 'seattle', ''], + ['full_example', 'san-diego', ''] + ], + 'j': [ + ['small_example', 'new-york', ''], + ['small_example', 'chicago', ''], + ['small_example', 'topeka', ''], + ['full_example', 'new-york', ''], + ['full_example', 'chicago', ''], + ['full_example', 'topeka', ''] + ], + 'd': [ + ['small_example', 'seattle', 'new-york', 2.5], + ['small_example', 'seattle', 'chicago', 1.7], + ['small_example', 'seattle', 'topeka', 1.8], + ['small_example', 'san-diego', 'new-york', 2.5], + ['small_example', 'san-diego', 'chicago', 1.8], + ['small_example', 'san-diego', 'topeka', 1.4], + ['full_example', 'seattle', 'new-york', 2.5], + ['full_example', 'seattle', 'chicago', 1.7], + ['full_example', 'seattle', 'topeka', 1.8], + ['full_example', 'san-diego', 'new-york', 2.5], + ['full_example', 'san-diego', 'chicago', 1.8], + ['full_example', 'san-diego', 'topeka', 1.4] + ], + 'a': [ + ['full_example', 'seattle', 350.0], + ['full_example', 'san-diego', 600.0] + ], + 'b': [ + ['full_example', 'new-york', 325.0], + ['full_example', 'chicago', 300.0], + ['full_example', 'topeka', 275.0] + ], + 'f': [ + ['full_example', 90.0] + ], + 'c': [ + ['full_example', 'seattle', 'new-york', 0.225], + ['full_example', 'seattle', 'chicago', 0.153], + ['full_example', 'seattle', 'topeka', 0.162], + ['full_example', 'san-diego', 'new-york', 0.225], + ['full_example', 'san-diego', 'chicago', 0.162], + ['full_example', 'san-diego', 'topeka', 0.12599999999999997] + ], + 'x': [ + ['full_example', 'seattle', 'new-york', 50.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'seattle', 'chicago', 300.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'seattle', 'topeka', 0.0, 0.036, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'new-york', 275.0, 0.0, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'chicago', 0.0, 0.009, 0.0, float('inf'), 1.0], + ['full_example', 'san-diego', 'topeka', 275.0, 0.0, 0.0, float('inf'), 1.0] + ], + 'z': [ + ['full_example', 153.675, 0.0, float('-inf'), float('inf'), 1.0] + ], + 'cost': [ + ['full_example', 0.0, 1.0, 0.0, 0.0, 1.0] + ], + 'supply': [ + ['full_example', 'seattle', 350.0, 0.0, float('-inf'), 350.0, 1.0], + ['full_example', 'san-diego', 550.0, 0.0, float('-inf'), 600.0, 1.0] + ], + 'demand': [ + ['full_example', 'new-york', 325.0, 0.225, 325.0, float('inf'), 1.0], + ['full_example', 'chicago', 300.0, 0.153, 300.0, float('inf'), 1.0], + ['full_example', 'topeka', 275.0, 0.126, 275.0, float('inf'), 1.0] + ] + } + self.check_gdx_file(symbols, ['small_example', 'full_example']) diff --git a/src/tools/tests/main.py b/src/tools/tests/main.py new file mode 100644 index 0000000..0e1f8c5 --- /dev/null +++ b/src/tools/tests/main.py @@ -0,0 +1,60 @@ +import sys +import argparse +import unittest +import gams +from gdxdump import TestGdxDump +from gdxdiff import TestGdxDiff +from gdxmerge import TestGdxMerge + + +class TestGdxTools(unittest.TestCase): + + def test_gams_module(self) -> None: + self.assertTrue('gams' in sys.modules) + + def test_gams_api(self) -> None: + self.assertRegex( + f'API OK -- Version {gams.__version__}', + r'API OK -- Version \d+\.\d+\.\d+' + ) + + +def main() -> int: + parser = argparse.ArgumentParser(description='gdxtools tests') + parser.add_argument('-t', '--tool', help='specify the tool you want to test') + args = parser.parse_args() + + def suite() -> unittest.TestSuite: + suite = unittest.TestSuite() + loader = unittest.TestLoader() + + suite.addTests(loader.loadTestsFromTestCase(TestGdxTools)) + + match args.tool: + case 'gdxdump' | 'dump': + suite.addTests(loader.loadTestsFromTestCase(TestGdxDump)) + + case 'gdxdiff' | 'diff': + suite.addTests(loader.loadTestsFromTestCase(TestGdxDiff)) + + case 'gdxmerge' | 'merge': + suite.addTests(loader.loadTestsFromTestCase(TestGdxMerge)) + + case _: + suite.addTests(loader.loadTestsFromTestCase(TestGdxDump)) + suite.addTests(loader.loadTestsFromTestCase(TestGdxDiff)) + suite.addTests(loader.loadTestsFromTestCase(TestGdxMerge)) + + return suite + + runner = unittest.TextTestRunner() + result = runner.run(suite()) + + if result.wasSuccessful(): + return 0 + else: + return 1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/tools/tests/output/gdxdiff/default_values_example_1_and_default_values_example_2.txt b/src/tools/tests/output/gdxdiff/default_values_example_1_and_default_values_example_2.txt new file mode 100644 index 0000000..9bc9644 --- /dev/null +++ b/src/tools/tests/output/gdxdiff/default_values_example_1_and_default_values_example_2.txt @@ -0,0 +1,6 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : default_values_example_1.gdx +File2 : default_values_example_2.gdx +No differences found +Output: diffile.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/default_values_example_1_and_default_values_example_2_compare_default_values.txt b/src/tools/tests/output/gdxdiff/default_values_example_1_and_default_values_example_2_compare_default_values.txt new file mode 100644 index 0000000..a356b82 --- /dev/null +++ b/src/tools/tests/output/gdxdiff/default_values_example_1_and_default_values_example_2_compare_default_values.txt @@ -0,0 +1,7 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : default_values_example_1.gdx +File2 : default_values_example_2.gdx +Summary of differences: +v Keys are different +Output: diffile.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/description_example_1_and_description_example_2_compare_associated_texts_n.txt b/src/tools/tests/output/gdxdiff/description_example_1_and_description_example_2_compare_associated_texts_n.txt new file mode 100644 index 0000000..4d7fcd2 --- /dev/null +++ b/src/tools/tests/output/gdxdiff/description_example_1_and_description_example_2_compare_associated_texts_n.txt @@ -0,0 +1,6 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : description_example_1.gdx +File2 : description_example_2.gdx +No differences found +Output: diff_file.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/description_example_1_and_description_example_2_compare_associated_texts_y.txt b/src/tools/tests/output/gdxdiff/description_example_1_and_description_example_2_compare_associated_texts_y.txt new file mode 100644 index 0000000..974b16d --- /dev/null +++ b/src/tools/tests/output/gdxdiff/description_example_1_and_description_example_2_compare_associated_texts_y.txt @@ -0,0 +1,7 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : description_example_1.gdx +File2 : description_example_2.gdx +Summary of differences: +i Data are different +Output: diff_file.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/domain_example_1_and_domain_example_2.txt b/src/tools/tests/output/gdxdiff/domain_example_1_and_domain_example_2.txt new file mode 100644 index 0000000..6f86f38 --- /dev/null +++ b/src/tools/tests/output/gdxdiff/domain_example_1_and_domain_example_2.txt @@ -0,0 +1,8 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : domain_example_1.gdx +File2 : domain_example_2.gdx +Summary of differences: +i Symbol not found in file 2 +j Symbol not found in file 1 +Output: diff_file.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/domain_example_1_and_domain_example_2_compare_symbol_domains.txt b/src/tools/tests/output/gdxdiff/domain_example_1_and_domain_example_2_compare_symbol_domains.txt new file mode 100644 index 0000000..5b826d5 --- /dev/null +++ b/src/tools/tests/output/gdxdiff/domain_example_1_and_domain_example_2_compare_symbol_domains.txt @@ -0,0 +1,9 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : domain_example_1.gdx +File2 : domain_example_2.gdx +Summary of differences: +a Domains are different +i Symbol not found in file 2 +j Symbol not found in file 1 +Output: diff_file.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_data_and_variables.txt b/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_data_and_variables.txt new file mode 100644 index 0000000..261f935 --- /dev/null +++ b/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_data_and_variables.txt @@ -0,0 +1,9 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : full_example.gdx +File2 : full_example_changed_data_and_variables.gdx +Summary of differences: +c Data are different +d Data are different +x Data are different +Output: diffile.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_data_and_variables_id_c_d.txt b/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_data_and_variables_id_c_d.txt new file mode 100644 index 0000000..e490e01 --- /dev/null +++ b/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_data_and_variables_id_c_d.txt @@ -0,0 +1,9 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : full_example.gdx +File2 : full_example_changed_data_and_variables.gdx +ID : c d +Summary of differences: +c Data are different +d Data are different +Output: diffile.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_data_and_variables_id_x.txt b/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_data_and_variables_id_x.txt new file mode 100644 index 0000000..6764344 --- /dev/null +++ b/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_data_and_variables_id_x.txt @@ -0,0 +1,8 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : full_example.gdx +File2 : full_example_changed_data_and_variables.gdx +ID : x +Summary of differences: +x Data are different +Output: diffile.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_data_and_variables_skip_id_c_d.txt b/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_data_and_variables_skip_id_c_d.txt new file mode 100644 index 0000000..86c2aed --- /dev/null +++ b/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_data_and_variables_skip_id_c_d.txt @@ -0,0 +1,8 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : full_example.gdx +File2 : full_example_changed_data_and_variables.gdx +SkipID: c d +Summary of differences: +x Data are different +Output: diffile.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_data_and_variables_skip_id_x.txt b/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_data_and_variables_skip_id_x.txt new file mode 100644 index 0000000..00eea0d --- /dev/null +++ b/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_data_and_variables_skip_id_x.txt @@ -0,0 +1,9 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : full_example.gdx +File2 : full_example_changed_data_and_variables.gdx +SkipID: x +Summary of differences: +c Data are different +d Data are different +Output: diffile.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_variables.txt b/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_variables.txt new file mode 100644 index 0000000..c49dfaa --- /dev/null +++ b/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_variables.txt @@ -0,0 +1,7 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : full_example.gdx +File2 : changed_full_example.gdx +Summary of differences: +x Data are different +Output: diffile.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_variables_field_m.txt b/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_variables_field_m.txt new file mode 100644 index 0000000..3f6fe7e --- /dev/null +++ b/src/tools/tests/output/gdxdiff/full_example_and_full_example_changed_variables_field_m.txt @@ -0,0 +1,6 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : full_example.gdx +File2 : changed_full_example.gdx +No differences found +Output: diffile.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/order_example_1_and_order_example_2.txt b/src/tools/tests/output/gdxdiff/order_example_1_and_order_example_2.txt new file mode 100644 index 0000000..2d9ea55 --- /dev/null +++ b/src/tools/tests/output/gdxdiff/order_example_1_and_order_example_2.txt @@ -0,0 +1,9 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : test_1.gdx +File2 : test_2.gdx +Summary of differences: +t1 Keys are different +t2 Keys are different +t3 Keys are different +Output: diffile.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/small_example_and_full_example.txt b/src/tools/tests/output/gdxdiff/small_example_and_full_example.txt new file mode 100644 index 0000000..2c34570 --- /dev/null +++ b/src/tools/tests/output/gdxdiff/small_example_and_full_example.txt @@ -0,0 +1,15 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : small_example.gdx +File2 : full_example.gdx +Summary of differences: + a Symbol not found in file 1 + b Symbol not found in file 1 + c Symbol not found in file 1 + cost Symbol not found in file 1 +demand Symbol not found in file 1 + f Symbol not found in file 1 +supply Symbol not found in file 1 + x Symbol not found in file 1 + z Symbol not found in file 1 +Output: diffile.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/small_example_and_small_example_changed_data.txt b/src/tools/tests/output/gdxdiff/small_example_and_small_example_changed_data.txt new file mode 100644 index 0000000..d0aa70a --- /dev/null +++ b/src/tools/tests/output/gdxdiff/small_example_and_small_example_changed_data.txt @@ -0,0 +1,7 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : small_example.gdx +File2 : changed_small_example.gdx +Summary of differences: +d Data are different +Output: diffile.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/small_example_and_small_example_changed_data_epsilon.txt b/src/tools/tests/output/gdxdiff/small_example_and_small_example_changed_data_epsilon.txt new file mode 100644 index 0000000..315760f --- /dev/null +++ b/src/tools/tests/output/gdxdiff/small_example_and_small_example_changed_data_epsilon.txt @@ -0,0 +1,6 @@ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS +File1 : small_example.gdx +File2 : changed_small_example.gdx +No differences found +Output: diffile.gdx +GDXDiff finished diff --git a/src/tools/tests/output/gdxdiff/usage.txt b/src/tools/tests/output/gdxdiff/usage.txt new file mode 100644 index 0000000..8a10da2 --- /dev/null +++ b/src/tools/tests/output/gdxdiff/usage.txt @@ -0,0 +1,20 @@ + +gdxdiff: GDX file differ +GDXDIFF 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS + +Usage: + gdxdiff file1.gdx file2.gdx [diffile.gdx] [options] + Options: + Eps = Val epsilon for comparison + RelEps = Val epsilon for relative comparison + Field = gamsfield (L, M, Up, Lo, Prior, Scale or All) + FldOnly write var or equ as parameter for selected field + DiffOnly write var or equ as parameter with field as an extra dimension + CmpDefaults compare default values + CmpDomains compare domains + MatrixFile compare GAMS matrix files in GDX format + IgnoreOrder ignore UEL order of input files - reduces size of output file + SetDesc = Y|N compare explanatory texts for set elements, activated by default (=Y) + ID = one or more identifiers; only ids listed will be compared + SkipID = one or more identifiers; ids listed will be skipped + The .gdx file extension is the default diff --git a/src/tools/tests/output/gdxdump/element_text_example_set_text.txt b/src/tools/tests/output/gdxdump/element_text_example_set_text.txt new file mode 100644 index 0000000..b2274f5 --- /dev/null +++ b/src/tools/tests/output/gdxdump/element_text_example_set_text.txt @@ -0,0 +1,9 @@ +Count of set text strings in GDX: 6 +max 0-based textIdx found in GDX: 5 + idx text + 0 "" + 1 "text 1" + 2 "text 2" + 3 "text 3" + 4 "text 4" + 5 "text 5" diff --git a/src/tools/tests/output/gdxdump/element_text_example_symbol_format_csv_set_text.txt b/src/tools/tests/output/gdxdump/element_text_example_symbol_format_csv_set_text.txt new file mode 100644 index 0000000..604d87b --- /dev/null +++ b/src/tools/tests/output/gdxdump/element_text_example_symbol_format_csv_set_text.txt @@ -0,0 +1,4 @@ +"Dim1","Text" +"new-york","text 3" +"chicago","text 4" +"topeka","text 5" diff --git a/src/tools/tests/output/gdxdump/full_example.txt b/src/tools/tests/output/gdxdump/full_example.txt new file mode 100644 index 0000000..8b1833f --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example.txt @@ -0,0 +1,73 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter a(*) capacity of plant i in cases / +'seattle' 350, +'san-diego' 600 /; + +Parameter b(j) demand at market j in cases / +'new-york' 325, +'chicago' 300, +'topeka' 275 /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' 2.5, +'seattle'.'chicago' 1.7, +'seattle'.'topeka' 1.8, +'san-diego'.'new-york' 2.5, +'san-diego'.'chicago' 1.8, +'san-diego'.'topeka' 1.4 /; + +Scalar f freight in dollars per case per thousand miles / 90 /; + +Parameter c(i,j) transport cost in thousands of dollars per case / +'seattle'.'new-york' 0.225, +'seattle'.'chicago' 0.153, +'seattle'.'topeka' 0.162, +'san-diego'.'new-york' 0.225, +'san-diego'.'chicago' 0.162, +'san-diego'.'topeka' 0.126 /; + +positive Variable x(i,j) shipment quantities in cases / +'seattle'.'new-york'.L 50, +'seattle'.'chicago'.L 300, +'seattle'.'topeka'.M 0.036, +'san-diego'.'new-york'.L 275, +'san-diego'.'chicago'.M 0.009, +'san-diego'.'topeka'.L 275 /; + +free Variable z total transportation costs in thousands of dollars /L 153.675 /; + +Equation cost define objective function /M 1, LO 0, UP 0 /; + +Equation supply(i) observe supply limit at plant i / +'seattle'.L 350, +'seattle'.LO -Inf, +'seattle'.UP 350, +'san-diego'.L 550, +'san-diego'.LO -Inf, +'san-diego'.UP 600 /; + +Equation demand(j) satisfy demand at market j / +'new-york'.L 325, +'new-york'.M 0.225, +'new-york'.LO 325, +'new-york'.UP +Inf, +'chicago'.L 300, +'chicago'.M 0.153, +'chicago'.LO 300, +'chicago'.UP +Inf, +'topeka'.L 275, +'topeka'.M 0.126, +'topeka'.LO 275, +'topeka'.UP +Inf /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/full_example_decimal_separator_comma.txt b/src/tools/tests/output/gdxdump/full_example_decimal_separator_comma.txt new file mode 100644 index 0000000..84ac271 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_decimal_separator_comma.txt @@ -0,0 +1,73 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter a(*) capacity of plant i in cases / +'seattle' 350, +'san-diego' 600 /; + +Parameter b(j) demand at market j in cases / +'new-york' 325, +'chicago' 300, +'topeka' 275 /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' 2,5, +'seattle'.'chicago' 1,7, +'seattle'.'topeka' 1,8, +'san-diego'.'new-york' 2,5, +'san-diego'.'chicago' 1,8, +'san-diego'.'topeka' 1,4 /; + +Scalar f freight in dollars per case per thousand miles / 90 /; + +Parameter c(i,j) transport cost in thousands of dollars per case / +'seattle'.'new-york' 0,225, +'seattle'.'chicago' 0,153, +'seattle'.'topeka' 0,162, +'san-diego'.'new-york' 0,225, +'san-diego'.'chicago' 0,162, +'san-diego'.'topeka' 0,126 /; + +positive Variable x(i,j) shipment quantities in cases / +'seattle'.'new-york'.L 50, +'seattle'.'chicago'.L 300, +'seattle'.'topeka'.M 0,036, +'san-diego'.'new-york'.L 275, +'san-diego'.'chicago'.M 0,009, +'san-diego'.'topeka'.L 275 /; + +free Variable z total transportation costs in thousands of dollars /L 153,675 /; + +Equation cost define objective function /M 1, LO 0, UP 0 /; + +Equation supply(i) observe supply limit at plant i / +'seattle'.L 350, +'seattle'.LO -Inf, +'seattle'.UP 350, +'san-diego'.L 550, +'san-diego'.LO -Inf, +'san-diego'.UP 600 /; + +Equation demand(j) satisfy demand at market j / +'new-york'.L 325, +'new-york'.M 0,225, +'new-york'.LO 325, +'new-york'.UP +Inf, +'chicago'.L 300, +'chicago'.M 0,153, +'chicago'.LO 300, +'chicago'.UP +Inf, +'topeka'.L 275, +'topeka'.M 0,126, +'topeka'.LO 275, +'topeka'.UP +Inf /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/full_example_decimal_separator_period.txt b/src/tools/tests/output/gdxdump/full_example_decimal_separator_period.txt new file mode 100644 index 0000000..8b1833f --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_decimal_separator_period.txt @@ -0,0 +1,73 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter a(*) capacity of plant i in cases / +'seattle' 350, +'san-diego' 600 /; + +Parameter b(j) demand at market j in cases / +'new-york' 325, +'chicago' 300, +'topeka' 275 /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' 2.5, +'seattle'.'chicago' 1.7, +'seattle'.'topeka' 1.8, +'san-diego'.'new-york' 2.5, +'san-diego'.'chicago' 1.8, +'san-diego'.'topeka' 1.4 /; + +Scalar f freight in dollars per case per thousand miles / 90 /; + +Parameter c(i,j) transport cost in thousands of dollars per case / +'seattle'.'new-york' 0.225, +'seattle'.'chicago' 0.153, +'seattle'.'topeka' 0.162, +'san-diego'.'new-york' 0.225, +'san-diego'.'chicago' 0.162, +'san-diego'.'topeka' 0.126 /; + +positive Variable x(i,j) shipment quantities in cases / +'seattle'.'new-york'.L 50, +'seattle'.'chicago'.L 300, +'seattle'.'topeka'.M 0.036, +'san-diego'.'new-york'.L 275, +'san-diego'.'chicago'.M 0.009, +'san-diego'.'topeka'.L 275 /; + +free Variable z total transportation costs in thousands of dollars /L 153.675 /; + +Equation cost define objective function /M 1, LO 0, UP 0 /; + +Equation supply(i) observe supply limit at plant i / +'seattle'.L 350, +'seattle'.LO -Inf, +'seattle'.UP 350, +'san-diego'.L 550, +'san-diego'.LO -Inf, +'san-diego'.UP 600 /; + +Equation demand(j) satisfy demand at market j / +'new-york'.L 325, +'new-york'.M 0.225, +'new-york'.LO 325, +'new-york'.UP +Inf, +'chicago'.L 300, +'chicago'.M 0.153, +'chicago'.LO 300, +'chicago'.UP +Inf, +'topeka'.L 275, +'topeka'.M 0.126, +'topeka'.LO 275, +'topeka'.UP +Inf /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/full_example_delimiter_blank.txt b/src/tools/tests/output/gdxdump/full_example_delimiter_blank.txt new file mode 100644 index 0000000..0ae303f --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_delimiter_blank.txt @@ -0,0 +1,73 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter a(*) capacity of plant i in cases / +'seattle' 350, +'san-diego' 600 /; + +Parameter b(j) demand at market j in cases / +'new-york' 325, +'chicago' 300, +'topeka' 275 /; + +Parameter d(i,j) distance in thousands of miles / +'seattle' 'new-york' 2.5, +'seattle' 'chicago' 1.7, +'seattle' 'topeka' 1.8, +'san-diego' 'new-york' 2.5, +'san-diego' 'chicago' 1.8, +'san-diego' 'topeka' 1.4 /; + +Scalar f freight in dollars per case per thousand miles / 90 /; + +Parameter c(i,j) transport cost in thousands of dollars per case / +'seattle' 'new-york' 0.225, +'seattle' 'chicago' 0.153, +'seattle' 'topeka' 0.162, +'san-diego' 'new-york' 0.225, +'san-diego' 'chicago' 0.162, +'san-diego' 'topeka' 0.126 /; + +positive Variable x(i,j) shipment quantities in cases / +'seattle' 'new-york' L 50, +'seattle' 'chicago' L 300, +'seattle' 'topeka' M 0.036, +'san-diego' 'new-york' L 275, +'san-diego' 'chicago' M 0.009, +'san-diego' 'topeka' L 275 /; + +free Variable z total transportation costs in thousands of dollars /L 153.675 /; + +Equation cost define objective function /M 1, LO 0, UP 0 /; + +Equation supply(i) observe supply limit at plant i / +'seattle' L 350, +'seattle' LO -Inf, +'seattle' UP 350, +'san-diego' L 550, +'san-diego' LO -Inf, +'san-diego' UP 600 /; + +Equation demand(j) satisfy demand at market j / +'new-york' L 325, +'new-york' M 0.225, +'new-york' LO 325, +'new-york' UP +Inf, +'chicago' L 300, +'chicago' M 0.153, +'chicago' LO 300, +'chicago' UP +Inf, +'topeka' L 275, +'topeka' M 0.126, +'topeka' LO 275, +'topeka' UP +Inf /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/full_example_delimiter_comma.txt b/src/tools/tests/output/gdxdump/full_example_delimiter_comma.txt new file mode 100644 index 0000000..089e47d --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_delimiter_comma.txt @@ -0,0 +1,73 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter a(*) capacity of plant i in cases / +'seattle' 350, +'san-diego' 600 /; + +Parameter b(j) demand at market j in cases / +'new-york' 325, +'chicago' 300, +'topeka' 275 /; + +Parameter d(i,j) distance in thousands of miles / +'seattle','new-york' 2.5, +'seattle','chicago' 1.7, +'seattle','topeka' 1.8, +'san-diego','new-york' 2.5, +'san-diego','chicago' 1.8, +'san-diego','topeka' 1.4 /; + +Scalar f freight in dollars per case per thousand miles / 90 /; + +Parameter c(i,j) transport cost in thousands of dollars per case / +'seattle','new-york' 0.225, +'seattle','chicago' 0.153, +'seattle','topeka' 0.162, +'san-diego','new-york' 0.225, +'san-diego','chicago' 0.162, +'san-diego','topeka' 0.126 /; + +positive Variable x(i,j) shipment quantities in cases / +'seattle','new-york',L 50, +'seattle','chicago',L 300, +'seattle','topeka',M 0.036, +'san-diego','new-york',L 275, +'san-diego','chicago',M 0.009, +'san-diego','topeka',L 275 /; + +free Variable z total transportation costs in thousands of dollars /L 153.675 /; + +Equation cost define objective function /M 1, LO 0, UP 0 /; + +Equation supply(i) observe supply limit at plant i / +'seattle',L 350, +'seattle',LO -Inf, +'seattle',UP 350, +'san-diego',L 550, +'san-diego',LO -Inf, +'san-diego',UP 600 /; + +Equation demand(j) satisfy demand at market j / +'new-york',L 325, +'new-york',M 0.225, +'new-york',LO 325, +'new-york',UP +Inf, +'chicago',L 300, +'chicago',M 0.153, +'chicago',LO 300, +'chicago',UP +Inf, +'topeka',L 275, +'topeka',M 0.126, +'topeka',LO 275, +'topeka',UP +Inf /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/full_example_delimiter_missing.txt b/src/tools/tests/output/gdxdump/full_example_delimiter_missing.txt new file mode 100644 index 0000000..587d503 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_delimiter_missing.txt @@ -0,0 +1,36 @@ +Unrecognized delimiter +gdxdump: Write GDX file in ASCII +GDXDUMP 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS + +Usage: +gdxdump + + -V or -Version Write version info of input file only + Output= Write output to file + Symb= Select a single identifier + UelTable= Include all unique elements + Delim=[period, comma, tab, blank, semicolon] + Specify a dimension delimiter + DecimalSep=[period, comma] + Specify a decimal separator + NoHeader Suppress writing of the headers + NoData Write headers only; no data + CSVAllFields When writing CSV write all variable/equation fields + CSVSetText When writing CSV write set element text + Symbols Get a list of all symbols + DomainInfo Get a list of all symbols showing domain information + SymbolsAsSet Get a list of all symbols as data for a set + SymbolsAsSetDI Get a list of all symbols as data for a set includes domain information + SetText Show the list of set text (aka associated text) + Format=[normal, gamsbas, csv] + dFormat=[normal, hexponential, hexBytes] + CDim=[Y, N] Use last dimension as column headers + (for CSV format only; default=N) + FilterDef=[Y, N] Filter default values; default=Y + EpsOut= String to be used when writing the value for EPS; default=EPS + NaOut= String to be used when writing the value for Not Available; default=NA + PinfOut= String to be used when writing the value for Positive Infinity; default=+Inf + MinfOut= String to be used when writing the value for Negative Infinity; default=-Inf + UndfOut= String to be used when writing the value for Undefined; default=Undf + ZeroOut= String to be used when writing the value for Zero; default=0 + Header= New header for CSV output format diff --git a/src/tools/tests/output/gdxdump/full_example_delimiter_period.txt b/src/tools/tests/output/gdxdump/full_example_delimiter_period.txt new file mode 100644 index 0000000..8b1833f --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_delimiter_period.txt @@ -0,0 +1,73 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter a(*) capacity of plant i in cases / +'seattle' 350, +'san-diego' 600 /; + +Parameter b(j) demand at market j in cases / +'new-york' 325, +'chicago' 300, +'topeka' 275 /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' 2.5, +'seattle'.'chicago' 1.7, +'seattle'.'topeka' 1.8, +'san-diego'.'new-york' 2.5, +'san-diego'.'chicago' 1.8, +'san-diego'.'topeka' 1.4 /; + +Scalar f freight in dollars per case per thousand miles / 90 /; + +Parameter c(i,j) transport cost in thousands of dollars per case / +'seattle'.'new-york' 0.225, +'seattle'.'chicago' 0.153, +'seattle'.'topeka' 0.162, +'san-diego'.'new-york' 0.225, +'san-diego'.'chicago' 0.162, +'san-diego'.'topeka' 0.126 /; + +positive Variable x(i,j) shipment quantities in cases / +'seattle'.'new-york'.L 50, +'seattle'.'chicago'.L 300, +'seattle'.'topeka'.M 0.036, +'san-diego'.'new-york'.L 275, +'san-diego'.'chicago'.M 0.009, +'san-diego'.'topeka'.L 275 /; + +free Variable z total transportation costs in thousands of dollars /L 153.675 /; + +Equation cost define objective function /M 1, LO 0, UP 0 /; + +Equation supply(i) observe supply limit at plant i / +'seattle'.L 350, +'seattle'.LO -Inf, +'seattle'.UP 350, +'san-diego'.L 550, +'san-diego'.LO -Inf, +'san-diego'.UP 600 /; + +Equation demand(j) satisfy demand at market j / +'new-york'.L 325, +'new-york'.M 0.225, +'new-york'.LO 325, +'new-york'.UP +Inf, +'chicago'.L 300, +'chicago'.M 0.153, +'chicago'.LO 300, +'chicago'.UP +Inf, +'topeka'.L 275, +'topeka'.M 0.126, +'topeka'.LO 275, +'topeka'.UP +Inf /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/full_example_delimiter_semicolon.txt b/src/tools/tests/output/gdxdump/full_example_delimiter_semicolon.txt new file mode 100644 index 0000000..4daf3ae --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_delimiter_semicolon.txt @@ -0,0 +1,73 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter a(*) capacity of plant i in cases / +'seattle' 350, +'san-diego' 600 /; + +Parameter b(j) demand at market j in cases / +'new-york' 325, +'chicago' 300, +'topeka' 275 /; + +Parameter d(i,j) distance in thousands of miles / +'seattle';'new-york' 2.5, +'seattle';'chicago' 1.7, +'seattle';'topeka' 1.8, +'san-diego';'new-york' 2.5, +'san-diego';'chicago' 1.8, +'san-diego';'topeka' 1.4 /; + +Scalar f freight in dollars per case per thousand miles / 90 /; + +Parameter c(i,j) transport cost in thousands of dollars per case / +'seattle';'new-york' 0.225, +'seattle';'chicago' 0.153, +'seattle';'topeka' 0.162, +'san-diego';'new-york' 0.225, +'san-diego';'chicago' 0.162, +'san-diego';'topeka' 0.126 /; + +positive Variable x(i,j) shipment quantities in cases / +'seattle';'new-york';L 50, +'seattle';'chicago';L 300, +'seattle';'topeka';M 0.036, +'san-diego';'new-york';L 275, +'san-diego';'chicago';M 0.009, +'san-diego';'topeka';L 275 /; + +free Variable z total transportation costs in thousands of dollars /L 153.675 /; + +Equation cost define objective function /M 1, LO 0, UP 0 /; + +Equation supply(i) observe supply limit at plant i / +'seattle';L 350, +'seattle';LO -Inf, +'seattle';UP 350, +'san-diego';L 550, +'san-diego';LO -Inf, +'san-diego';UP 600 /; + +Equation demand(j) satisfy demand at market j / +'new-york';L 325, +'new-york';M 0.225, +'new-york';LO 325, +'new-york';UP +Inf, +'chicago';L 300, +'chicago';M 0.153, +'chicago';LO 300, +'chicago';UP +Inf, +'topeka';L 275, +'topeka';M 0.126, +'topeka';LO 275, +'topeka';UP +Inf /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/full_example_delimiter_tab.txt b/src/tools/tests/output/gdxdump/full_example_delimiter_tab.txt new file mode 100644 index 0000000..7478b91 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_delimiter_tab.txt @@ -0,0 +1,73 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter a(*) capacity of plant i in cases / +'seattle' 350, +'san-diego' 600 /; + +Parameter b(j) demand at market j in cases / +'new-york' 325, +'chicago' 300, +'topeka' 275 /; + +Parameter d(i,j) distance in thousands of miles / +'seattle' 'new-york' 2.5, +'seattle' 'chicago' 1.7, +'seattle' 'topeka' 1.8, +'san-diego' 'new-york' 2.5, +'san-diego' 'chicago' 1.8, +'san-diego' 'topeka' 1.4 /; + +Scalar f freight in dollars per case per thousand miles / 90 /; + +Parameter c(i,j) transport cost in thousands of dollars per case / +'seattle' 'new-york' 0.225, +'seattle' 'chicago' 0.153, +'seattle' 'topeka' 0.162, +'san-diego' 'new-york' 0.225, +'san-diego' 'chicago' 0.162, +'san-diego' 'topeka' 0.126 /; + +positive Variable x(i,j) shipment quantities in cases / +'seattle' 'new-york' L 50, +'seattle' 'chicago' L 300, +'seattle' 'topeka' M 0.036, +'san-diego' 'new-york' L 275, +'san-diego' 'chicago' M 0.009, +'san-diego' 'topeka' L 275 /; + +free Variable z total transportation costs in thousands of dollars /L 153.675 /; + +Equation cost define objective function /M 1, LO 0, UP 0 /; + +Equation supply(i) observe supply limit at plant i / +'seattle' L 350, +'seattle' LO -Inf, +'seattle' UP 350, +'san-diego' L 550, +'san-diego' LO -Inf, +'san-diego' UP 600 /; + +Equation demand(j) satisfy demand at market j / +'new-york' L 325, +'new-york' M 0.225, +'new-york' LO 325, +'new-york' UP +Inf, +'chicago' L 300, +'chicago' M 0.153, +'chicago' LO 300, +'chicago' UP +Inf, +'topeka' L 275, +'topeka' M 0.126, +'topeka' LO 275, +'topeka' UP +Inf /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/full_example_domain_information.txt b/src/tools/tests/output/gdxdump/full_example_domain_information.txt new file mode 100644 index 0000000..45c3eeb --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_domain_information.txt @@ -0,0 +1,13 @@ +SyNr Type DomInf Symbol + 3 Par None a(*) + 4 Par Regular b(j) + 7 Par Regular c(i, j) + 10 Equ None cost + 5 Par Regular d(i, j) + 12 Equ Regular demand(j) + 6 Par None f + 1 Set None i(*) + 2 Set None j(*) + 11 Equ Regular supply(i) + 8 Var Regular x(i, j) + 9 Var None z diff --git a/src/tools/tests/output/gdxdump/full_example_filter_default_values.txt b/src/tools/tests/output/gdxdump/full_example_filter_default_values.txt new file mode 100644 index 0000000..61ab709 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_filter_default_values.txt @@ -0,0 +1,104 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter a(*) capacity of plant i in cases / +'seattle' 350, +'san-diego' 600 /; + +Parameter b(j) demand at market j in cases / +'new-york' 325, +'chicago' 300, +'topeka' 275 /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' 2.5, +'seattle'.'chicago' 1.7, +'seattle'.'topeka' 1.8, +'san-diego'.'new-york' 2.5, +'san-diego'.'chicago' 1.8, +'san-diego'.'topeka' 1.4 /; + +Scalar f freight in dollars per case per thousand miles / 90 /; + +Parameter c(i,j) transport cost in thousands of dollars per case / +'seattle'.'new-york' 0.225, +'seattle'.'chicago' 0.153, +'seattle'.'topeka' 0.162, +'san-diego'.'new-york' 0.225, +'san-diego'.'chicago' 0.162, +'san-diego'.'topeka' 0.126 /; + +positive Variable x(i,j) shipment quantities in cases / +'seattle'.'new-york'.L 50, +'seattle'.'new-york'.M 0, +'seattle'.'new-york'.LO 0, +'seattle'.'new-york'.UP +Inf, +'seattle'.'new-york'.SCALE 1, +'seattle'.'chicago'.L 300, +'seattle'.'chicago'.M 0, +'seattle'.'chicago'.LO 0, +'seattle'.'chicago'.UP +Inf, +'seattle'.'chicago'.SCALE 1, +'seattle'.'topeka'.L 0, +'seattle'.'topeka'.M 0.036, +'seattle'.'topeka'.LO 0, +'seattle'.'topeka'.UP +Inf, +'seattle'.'topeka'.SCALE 1, +'san-diego'.'new-york'.L 275, +'san-diego'.'new-york'.M 0, +'san-diego'.'new-york'.LO 0, +'san-diego'.'new-york'.UP +Inf, +'san-diego'.'new-york'.SCALE 1, +'san-diego'.'chicago'.L 0, +'san-diego'.'chicago'.M 0.009, +'san-diego'.'chicago'.LO 0, +'san-diego'.'chicago'.UP +Inf, +'san-diego'.'chicago'.SCALE 1, +'san-diego'.'topeka'.L 275, +'san-diego'.'topeka'.M 0, +'san-diego'.'topeka'.LO 0, +'san-diego'.'topeka'.UP +Inf, +'san-diego'.'topeka'.SCALE 1 /; + +free Variable z total transportation costs in thousands of dollars /L 153.675, M 0, LO -Inf, UP +Inf, SCALE 1 /; + +Equation cost define objective function /L 0, M 1, LO 0, UP 0, SCALE 1 /; + +Equation supply(i) observe supply limit at plant i / +'seattle'.L 350, +'seattle'.M 0, +'seattle'.LO -Inf, +'seattle'.UP 350, +'seattle'.SCALE 1, +'san-diego'.L 550, +'san-diego'.M 0, +'san-diego'.LO -Inf, +'san-diego'.UP 600, +'san-diego'.SCALE 1 /; + +Equation demand(j) satisfy demand at market j / +'new-york'.L 325, +'new-york'.M 0.225, +'new-york'.LO 325, +'new-york'.UP +Inf, +'new-york'.SCALE 1, +'chicago'.L 300, +'chicago'.M 0.153, +'chicago'.LO 300, +'chicago'.UP +Inf, +'chicago'.SCALE 1, +'topeka'.L 275, +'topeka'.M 0.126, +'topeka'.LO 275, +'topeka'.UP +Inf, +'topeka'.SCALE 1 /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/full_example_format_csv.txt b/src/tools/tests/output/gdxdump/full_example_format_csv.txt new file mode 100644 index 0000000..5cfdf1a --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_format_csv.txt @@ -0,0 +1 @@ +Symbol not specified when writing a CSV file diff --git a/src/tools/tests/output/gdxdump/full_example_format_gamsbas.txt b/src/tools/tests/output/gdxdump/full_example_format_gamsbas.txt new file mode 100644 index 0000000..01b726b --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_format_gamsbas.txt @@ -0,0 +1,17 @@ +$onEmpty + x.L ('seattle'.'new-york') = 50 ; + x.L ('seattle'.'chicago') = 300 ; + x.M ('seattle'.'topeka') = 0.036 ; + x.L ('san-diego'.'new-york') = 275 ; + x.M ('san-diego'.'chicago') = 0.009 ; + x.L ('san-diego'.'topeka') = 275 ; +$offListing + z.L = 153.675 ; + cost.M = 1 ; + ; + demand.M ('new-york') = 0.225 ; + demand.M ('chicago') = 0.153 ; + demand.M ('topeka') = 0.126 ; + +$offEmpty +$onListing diff --git a/src/tools/tests/output/gdxdump/full_example_format_normal.txt b/src/tools/tests/output/gdxdump/full_example_format_normal.txt new file mode 100644 index 0000000..8b1833f --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_format_normal.txt @@ -0,0 +1,73 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter a(*) capacity of plant i in cases / +'seattle' 350, +'san-diego' 600 /; + +Parameter b(j) demand at market j in cases / +'new-york' 325, +'chicago' 300, +'topeka' 275 /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' 2.5, +'seattle'.'chicago' 1.7, +'seattle'.'topeka' 1.8, +'san-diego'.'new-york' 2.5, +'san-diego'.'chicago' 1.8, +'san-diego'.'topeka' 1.4 /; + +Scalar f freight in dollars per case per thousand miles / 90 /; + +Parameter c(i,j) transport cost in thousands of dollars per case / +'seattle'.'new-york' 0.225, +'seattle'.'chicago' 0.153, +'seattle'.'topeka' 0.162, +'san-diego'.'new-york' 0.225, +'san-diego'.'chicago' 0.162, +'san-diego'.'topeka' 0.126 /; + +positive Variable x(i,j) shipment quantities in cases / +'seattle'.'new-york'.L 50, +'seattle'.'chicago'.L 300, +'seattle'.'topeka'.M 0.036, +'san-diego'.'new-york'.L 275, +'san-diego'.'chicago'.M 0.009, +'san-diego'.'topeka'.L 275 /; + +free Variable z total transportation costs in thousands of dollars /L 153.675 /; + +Equation cost define objective function /M 1, LO 0, UP 0 /; + +Equation supply(i) observe supply limit at plant i / +'seattle'.L 350, +'seattle'.LO -Inf, +'seattle'.UP 350, +'san-diego'.L 550, +'san-diego'.LO -Inf, +'san-diego'.UP 600 /; + +Equation demand(j) satisfy demand at market j / +'new-york'.L 325, +'new-york'.M 0.225, +'new-york'.LO 325, +'new-york'.UP +Inf, +'chicago'.L 300, +'chicago'.M 0.153, +'chicago'.LO 300, +'chicago'.UP +Inf, +'topeka'.L 275, +'topeka'.M 0.126, +'topeka'.LO 275, +'topeka'.UP +Inf /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/full_example_no_data.txt b/src/tools/tests/output/gdxdump/full_example_no_data.txt new file mode 100644 index 0000000..6b69c92 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_no_data.txt @@ -0,0 +1,43 @@ + +$gdxIn full_example.gdx +$onEmpty +$offEolCom +$eolCom !! + +Set i(*) supply ; +$loadDC i + +Set j(*) markets ; +$loadDC j + +Parameter a(*) capacity of plant i in cases ; +$loadDC a + +Parameter b(j) demand at market j in cases ; +$loadDC b + +Parameter d(i,j) distance in thousands of miles ; +$loadDC d + +Scalar f freight in dollars per case per thousand miles ; +$loadDC f + +Parameter c(i,j) transport cost in thousands of dollars per case ; +$loadDC c + +positive Variable x(i,j) shipment quantities in cases ; +$loadDC x + +free Variable z total transportation costs in thousands of dollars ; +$loadDC z + +Equation cost define objective function ; +$loadDC cost + +Equation supply(i) observe supply limit at plant i ; +$loadDC supply + +Equation demand(j) satisfy demand at market j ; +$loadDC demand + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/full_example_numerical_format_hexbytes.txt b/src/tools/tests/output/gdxdump/full_example_numerical_format_hexbytes.txt new file mode 100644 index 0000000..d8e8c74 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_numerical_format_hexbytes.txt @@ -0,0 +1,73 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter a(*) capacity of plant i in cases / +'seattle' 0x4075e00000000000, +'san-diego' 0x4082c00000000000 /; + +Parameter b(j) demand at market j in cases / +'new-york' 0x4074500000000000, +'chicago' 0x4072c00000000000, +'topeka' 0x4071300000000000 /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' 0x4004000000000000, +'seattle'.'chicago' 0x3ffb333333333333, +'seattle'.'topeka' 0x3ffccccccccccccd, +'san-diego'.'new-york' 0x4004000000000000, +'san-diego'.'chicago' 0x3ffccccccccccccd, +'san-diego'.'topeka' 0x3ff6666666666666 /; + +Scalar f freight in dollars per case per thousand miles / 0x4056800000000000 /; + +Parameter c(i,j) transport cost in thousands of dollars per case / +'seattle'.'new-york' 0x3fcccccccccccccd, +'seattle'.'chicago' 0x3fc395810624dd2f, +'seattle'.'topeka' 0x3fc4bc6a7ef9db23, +'san-diego'.'new-york' 0x3fcccccccccccccd, +'san-diego'.'chicago' 0x3fc4bc6a7ef9db23, +'san-diego'.'topeka' 0x3fc020c49ba5e353 /; + +positive Variable x(i,j) shipment quantities in cases / +'seattle'.'new-york'.L 0x4049000000000000, +'seattle'.'chicago'.L 0x4072c00000000000, +'seattle'.'topeka'.M 0x3fa26e978d4fdf3b, +'san-diego'.'new-york'.L 0x4071300000000000, +'san-diego'.'chicago'.M 0x3f826e978d4fdf3b, +'san-diego'.'topeka'.L 0x4071300000000000 /; + +free Variable z total transportation costs in thousands of dollars /L 0x406335999999999a /; + +Equation cost define objective function /M 0x3ff0000000000000, LO 0x0000000000000000, UP 0x0000000000000000 /; + +Equation supply(i) observe supply limit at plant i / +'seattle'.L 0x4075e00000000000, +'seattle'.LO -Inf, +'seattle'.UP 0x4075e00000000000, +'san-diego'.L 0x4081300000000000, +'san-diego'.LO -Inf, +'san-diego'.UP 0x4082c00000000000 /; + +Equation demand(j) satisfy demand at market j / +'new-york'.L 0x4074500000000000, +'new-york'.M 0x3fcccccccccccccd, +'new-york'.LO 0x4074500000000000, +'new-york'.UP +Inf, +'chicago'.L 0x4072c00000000000, +'chicago'.M 0x3fc395810624dd2f, +'chicago'.LO 0x4072c00000000000, +'chicago'.UP +Inf, +'topeka'.L 0x4071300000000000, +'topeka'.M 0x3fc020c49ba5e354, +'topeka'.LO 0x4071300000000000, +'topeka'.UP +Inf /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/full_example_numerical_format_hexponential.txt b/src/tools/tests/output/gdxdump/full_example_numerical_format_hexponential.txt new file mode 100644 index 0000000..7758e1b --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_numerical_format_hexponential.txt @@ -0,0 +1,73 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter a(*) capacity of plant i in cases / +'seattle' 0x1.5ep8, +'san-diego' 0x1.2cp9 /; + +Parameter b(j) demand at market j in cases / +'new-york' 0x1.45p8, +'chicago' 0x1.2cp8, +'topeka' 0x1.13p8 /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' 0x1.4p1, +'seattle'.'chicago' 0x1.b333333333333p0, +'seattle'.'topeka' 0x1.ccccccccccccdp0, +'san-diego'.'new-york' 0x1.4p1, +'san-diego'.'chicago' 0x1.ccccccccccccdp0, +'san-diego'.'topeka' 0x1.6666666666666p0 /; + +Scalar f freight in dollars per case per thousand miles / 0x1.68p6 /; + +Parameter c(i,j) transport cost in thousands of dollars per case / +'seattle'.'new-york' 0x1.ccccccccccccdp-3, +'seattle'.'chicago' 0x1.395810624dd2fp-3, +'seattle'.'topeka' 0x1.4bc6a7ef9db23p-3, +'san-diego'.'new-york' 0x1.ccccccccccccdp-3, +'san-diego'.'chicago' 0x1.4bc6a7ef9db23p-3, +'san-diego'.'topeka' 0x1.020c49ba5e353p-3 /; + +positive Variable x(i,j) shipment quantities in cases / +'seattle'.'new-york'.L 0x1.9p5, +'seattle'.'chicago'.L 0x1.2cp8, +'seattle'.'topeka'.M 0x1.26e978d4fdf3bp-5, +'san-diego'.'new-york'.L 0x1.13p8, +'san-diego'.'chicago'.M 0x1.26e978d4fdf3bp-7, +'san-diego'.'topeka'.L 0x1.13p8 /; + +free Variable z total transportation costs in thousands of dollars /L 0x1.335999999999ap7 /; + +Equation cost define objective function /M 0x1.0p0, LO 0x0.0p0, UP 0x0.0p0 /; + +Equation supply(i) observe supply limit at plant i / +'seattle'.L 0x1.5ep8, +'seattle'.LO -Inf, +'seattle'.UP 0x1.5ep8, +'san-diego'.L 0x1.13p9, +'san-diego'.LO -Inf, +'san-diego'.UP 0x1.2cp9 /; + +Equation demand(j) satisfy demand at market j / +'new-york'.L 0x1.45p8, +'new-york'.M 0x1.ccccccccccccdp-3, +'new-york'.LO 0x1.45p8, +'new-york'.UP +Inf, +'chicago'.L 0x1.2cp8, +'chicago'.M 0x1.395810624dd2fp-3, +'chicago'.LO 0x1.2cp8, +'chicago'.UP +Inf, +'topeka'.L 0x1.13p8, +'topeka'.M 0x1.020c49ba5e354p-3, +'topeka'.LO 0x1.13p8, +'topeka'.UP +Inf /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/full_example_numerical_format_normal.txt b/src/tools/tests/output/gdxdump/full_example_numerical_format_normal.txt new file mode 100644 index 0000000..8b1833f --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_numerical_format_normal.txt @@ -0,0 +1,73 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter a(*) capacity of plant i in cases / +'seattle' 350, +'san-diego' 600 /; + +Parameter b(j) demand at market j in cases / +'new-york' 325, +'chicago' 300, +'topeka' 275 /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' 2.5, +'seattle'.'chicago' 1.7, +'seattle'.'topeka' 1.8, +'san-diego'.'new-york' 2.5, +'san-diego'.'chicago' 1.8, +'san-diego'.'topeka' 1.4 /; + +Scalar f freight in dollars per case per thousand miles / 90 /; + +Parameter c(i,j) transport cost in thousands of dollars per case / +'seattle'.'new-york' 0.225, +'seattle'.'chicago' 0.153, +'seattle'.'topeka' 0.162, +'san-diego'.'new-york' 0.225, +'san-diego'.'chicago' 0.162, +'san-diego'.'topeka' 0.126 /; + +positive Variable x(i,j) shipment quantities in cases / +'seattle'.'new-york'.L 50, +'seattle'.'chicago'.L 300, +'seattle'.'topeka'.M 0.036, +'san-diego'.'new-york'.L 275, +'san-diego'.'chicago'.M 0.009, +'san-diego'.'topeka'.L 275 /; + +free Variable z total transportation costs in thousands of dollars /L 153.675 /; + +Equation cost define objective function /M 1, LO 0, UP 0 /; + +Equation supply(i) observe supply limit at plant i / +'seattle'.L 350, +'seattle'.LO -Inf, +'seattle'.UP 350, +'san-diego'.L 550, +'san-diego'.LO -Inf, +'san-diego'.UP 600 /; + +Equation demand(j) satisfy demand at market j / +'new-york'.L 325, +'new-york'.M 0.225, +'new-york'.LO 325, +'new-york'.UP +Inf, +'chicago'.L 300, +'chicago'.M 0.153, +'chicago'.LO 300, +'chicago'.UP +Inf, +'topeka'.L 275, +'topeka'.M 0.126, +'topeka'.LO 275, +'topeka'.UP +Inf /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/full_example_symbol.txt b/src/tools/tests/output/gdxdump/full_example_symbol.txt new file mode 100644 index 0000000..c636268 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_symbol.txt @@ -0,0 +1,4 @@ + +Set i(*) supply / +'seattle', +'san-diego' /; diff --git a/src/tools/tests/output/gdxdump/full_example_symbol_format_csv.txt b/src/tools/tests/output/gdxdump/full_example_symbol_format_csv.txt new file mode 100644 index 0000000..3a1a2d5 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_symbol_format_csv.txt @@ -0,0 +1,3 @@ +"Dim1","Val" +"seattle",350 +"san-diego",600 diff --git a/src/tools/tests/output/gdxdump/full_example_symbol_format_csv_all_fields.txt b/src/tools/tests/output/gdxdump/full_example_symbol_format_csv_all_fields.txt new file mode 100644 index 0000000..4125246 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_symbol_format_csv_all_fields.txt @@ -0,0 +1,4 @@ +"j","Val","Marginal","Lower","Upper","Scale" +"new-york",325,0.225,325,+Inf,1 +"chicago",300,0.153,300,+Inf,1 +"topeka",275,0.126,275,+Inf,1 diff --git a/src/tools/tests/output/gdxdump/full_example_symbol_format_csv_cdim.txt b/src/tools/tests/output/gdxdump/full_example_symbol_format_csv_cdim.txt new file mode 100644 index 0000000..2995961 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_symbol_format_csv_cdim.txt @@ -0,0 +1,3 @@ +"i","new-york","chicago","topeka" +"seattle",50,300,0 +"san-diego",275,0,275 diff --git a/src/tools/tests/output/gdxdump/full_example_symbol_format_csv_header.txt b/src/tools/tests/output/gdxdump/full_example_symbol_format_csv_header.txt new file mode 100644 index 0000000..ca847f5 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_symbol_format_csv_header.txt @@ -0,0 +1,3 @@ +Test +"seattle",350 +"san-diego",600 diff --git a/src/tools/tests/output/gdxdump/full_example_symbol_format_csv_header_missing_identifier.txt b/src/tools/tests/output/gdxdump/full_example_symbol_format_csv_header_missing_identifier.txt new file mode 100644 index 0000000..f90ba1a --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_symbol_format_csv_header_missing_identifier.txt @@ -0,0 +1,2 @@ +"seattle",350 +"san-diego",600 diff --git a/src/tools/tests/output/gdxdump/full_example_symbol_format_csv_no_header.txt b/src/tools/tests/output/gdxdump/full_example_symbol_format_csv_no_header.txt new file mode 100644 index 0000000..f90ba1a --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_symbol_format_csv_no_header.txt @@ -0,0 +1,2 @@ +"seattle",350 +"san-diego",600 diff --git a/src/tools/tests/output/gdxdump/full_example_symbol_missing_identifier.txt b/src/tools/tests/output/gdxdump/full_example_symbol_missing_identifier.txt new file mode 100644 index 0000000..979a5d0 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_symbol_missing_identifier.txt @@ -0,0 +1,36 @@ +Symbol missing +gdxdump: Write GDX file in ASCII +GDXDUMP 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS + +Usage: +gdxdump + + -V or -Version Write version info of input file only + Output= Write output to file + Symb= Select a single identifier + UelTable= Include all unique elements + Delim=[period, comma, tab, blank, semicolon] + Specify a dimension delimiter + DecimalSep=[period, comma] + Specify a decimal separator + NoHeader Suppress writing of the headers + NoData Write headers only; no data + CSVAllFields When writing CSV write all variable/equation fields + CSVSetText When writing CSV write set element text + Symbols Get a list of all symbols + DomainInfo Get a list of all symbols showing domain information + SymbolsAsSet Get a list of all symbols as data for a set + SymbolsAsSetDI Get a list of all symbols as data for a set includes domain information + SetText Show the list of set text (aka associated text) + Format=[normal, gamsbas, csv] + dFormat=[normal, hexponential, hexBytes] + CDim=[Y, N] Use last dimension as column headers + (for CSV format only; default=N) + FilterDef=[Y, N] Filter default values; default=Y + EpsOut= String to be used when writing the value for EPS; default=EPS + NaOut= String to be used when writing the value for Not Available; default=NA + PinfOut= String to be used when writing the value for Positive Infinity; default=+Inf + MinfOut= String to be used when writing the value for Negative Infinity; default=-Inf + UndfOut= String to be used when writing the value for Undefined; default=Undf + ZeroOut= String to be used when writing the value for Zero; default=0 + Header= New header for CSV output format diff --git a/src/tools/tests/output/gdxdump/full_example_symbol_not_found.txt b/src/tools/tests/output/gdxdump/full_example_symbol_not_found.txt new file mode 100644 index 0000000..c78db56 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_symbol_not_found.txt @@ -0,0 +1 @@ +Symbol not found: e diff --git a/src/tools/tests/output/gdxdump/full_example_symbols.txt b/src/tools/tests/output/gdxdump/full_example_symbols.txt new file mode 100644 index 0000000..cee20e5 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_symbols.txt @@ -0,0 +1,13 @@ + Symbol Dim Type Records Explanatory text + 1 a 1 Par 2 capacity of plant i in cases + 2 b 1 Par 3 demand at market j in cases + 3 c 2 Par 6 transport cost in thousands of dollars per case + 4 cost 0 Equ 1 define objective function + 5 d 2 Par 6 distance in thousands of miles + 6 demand 1 Equ 3 satisfy demand at market j + 7 f 0 Par 1 freight in dollars per case per thousand miles + 8 i 1 Set 2 supply + 9 j 1 Set 3 markets +10 supply 1 Equ 2 observe supply limit at plant i +11 x 2 Var 6 shipment quantities in cases +12 z 0 Var 1 total transportation costs in thousands of dollars diff --git a/src/tools/tests/output/gdxdump/full_example_symbols_as_set.txt b/src/tools/tests/output/gdxdump/full_example_symbols_as_set.txt new file mode 100644 index 0000000..33b0add --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_symbols_as_set.txt @@ -0,0 +1,15 @@ +alias (Symbol, Dim, Type, *); +set gdxitems(Symbol,Dim,Type) Items in the GDX file / +"i".1."Set" "supply", +"j".1."Set" "markets", +"a".1."Par" "capacity of plant i in cases", +"b".1."Par" "demand at market j in cases", +"d".2."Par" "distance in thousands of miles", +"f".0."Par" "freight in dollars per case per thousand miles", +"c".2."Par" "transport cost in thousands of dollars per case", +"x".2."Var" "shipment quantities in cases", +"z".0."Var" "total transportation costs in thousands of dollars", +"cost".0."Equ" "define objective function", +"supply".1."Equ" "observe supply limit at plant i", +"demand".1."Equ" "satisfy demand at market j" +/; diff --git a/src/tools/tests/output/gdxdump/full_example_symbols_as_set_domain_information.txt b/src/tools/tests/output/gdxdump/full_example_symbols_as_set_domain_information.txt new file mode 100644 index 0000000..389efe8 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_symbols_as_set_domain_information.txt @@ -0,0 +1,15 @@ +alias (Symbol, Dim, Type, Domain, *); +set gdxitemsDI(Symbol,Dim,Type,Domain) Items in the GDX file / +"i".1."Set"."*", +"j".1."Set"."*", +"a".1."Par"."*", +"b".1."Par"."j", +"d".2."Par"."i, j", +"f".0."Par"."", +"c".2."Par"."i, j", +"x".2."Var"."i, j", +"z".0."Var"."", +"cost".0."Equ"."", +"supply".1."Equ"."i", +"demand".1."Equ"."j" +/; diff --git a/src/tools/tests/output/gdxdump/full_example_uel_table.txt b/src/tools/tests/output/gdxdump/full_example_uel_table.txt new file mode 100644 index 0000000..4b0375e --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_uel_table.txt @@ -0,0 +1,80 @@ + +Set e / + 'seattle' , + 'san-diego' , + 'new-york' , + 'chicago' , + 'topeka' /; +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter a(*) capacity of plant i in cases / +'seattle' 350, +'san-diego' 600 /; + +Parameter b(j) demand at market j in cases / +'new-york' 325, +'chicago' 300, +'topeka' 275 /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' 2.5, +'seattle'.'chicago' 1.7, +'seattle'.'topeka' 1.8, +'san-diego'.'new-york' 2.5, +'san-diego'.'chicago' 1.8, +'san-diego'.'topeka' 1.4 /; + +Scalar f freight in dollars per case per thousand miles / 90 /; + +Parameter c(i,j) transport cost in thousands of dollars per case / +'seattle'.'new-york' 0.225, +'seattle'.'chicago' 0.153, +'seattle'.'topeka' 0.162, +'san-diego'.'new-york' 0.225, +'san-diego'.'chicago' 0.162, +'san-diego'.'topeka' 0.126 /; + +positive Variable x(i,j) shipment quantities in cases / +'seattle'.'new-york'.L 50, +'seattle'.'chicago'.L 300, +'seattle'.'topeka'.M 0.036, +'san-diego'.'new-york'.L 275, +'san-diego'.'chicago'.M 0.009, +'san-diego'.'topeka'.L 275 /; + +free Variable z total transportation costs in thousands of dollars /L 153.675 /; + +Equation cost define objective function /M 1, LO 0, UP 0 /; + +Equation supply(i) observe supply limit at plant i / +'seattle'.L 350, +'seattle'.LO -Inf, +'seattle'.UP 350, +'san-diego'.L 550, +'san-diego'.LO -Inf, +'san-diego'.UP 600 /; + +Equation demand(j) satisfy demand at market j / +'new-york'.L 325, +'new-york'.M 0.225, +'new-york'.LO 325, +'new-york'.UP +Inf, +'chicago'.L 300, +'chicago'.M 0.153, +'chicago'.LO 300, +'chicago'.UP +Inf, +'topeka'.L 275, +'topeka'.M 0.126, +'topeka'.LO 275, +'topeka'.UP +Inf /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/full_example_uel_table_missing_identifier.txt b/src/tools/tests/output/gdxdump/full_example_uel_table_missing_identifier.txt new file mode 100644 index 0000000..6da8e3c --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_uel_table_missing_identifier.txt @@ -0,0 +1,36 @@ +UELSetName missing +gdxdump: Write GDX file in ASCII +GDXDUMP 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS + +Usage: +gdxdump + + -V or -Version Write version info of input file only + Output= Write output to file + Symb= Select a single identifier + UelTable= Include all unique elements + Delim=[period, comma, tab, blank, semicolon] + Specify a dimension delimiter + DecimalSep=[period, comma] + Specify a decimal separator + NoHeader Suppress writing of the headers + NoData Write headers only; no data + CSVAllFields When writing CSV write all variable/equation fields + CSVSetText When writing CSV write set element text + Symbols Get a list of all symbols + DomainInfo Get a list of all symbols showing domain information + SymbolsAsSet Get a list of all symbols as data for a set + SymbolsAsSetDI Get a list of all symbols as data for a set includes domain information + SetText Show the list of set text (aka associated text) + Format=[normal, gamsbas, csv] + dFormat=[normal, hexponential, hexBytes] + CDim=[Y, N] Use last dimension as column headers + (for CSV format only; default=N) + FilterDef=[Y, N] Filter default values; default=Y + EpsOut= String to be used when writing the value for EPS; default=EPS + NaOut= String to be used when writing the value for Not Available; default=NA + PinfOut= String to be used when writing the value for Positive Infinity; default=+Inf + MinfOut= String to be used when writing the value for Negative Infinity; default=-Inf + UndfOut= String to be used when writing the value for Undefined; default=Undf + ZeroOut= String to be used when writing the value for Zero; default=0 + Header= New header for CSV output format diff --git a/src/tools/tests/output/gdxdump/full_example_version.txt b/src/tools/tests/output/gdxdump/full_example_version.txt new file mode 100644 index 0000000..289ea95 --- /dev/null +++ b/src/tools/tests/output/gdxdump/full_example_version.txt @@ -0,0 +1,6 @@ +* File version : GDX Library C++ V7 (AUDIT) Mon Aug 5 09:13:12 2024 arm64 macOS +* Producer : GAMS Transfer +* File format : 7 +* Compression : 0 +* Symbols : 12 +* Unique Elements: 5 diff --git a/src/tools/tests/output/gdxdump/label_example.txt b/src/tools/tests/output/gdxdump/label_example.txt new file mode 100644 index 0000000..e2bb396 --- /dev/null +++ b/src/tools/tests/output/gdxdump/label_example.txt @@ -0,0 +1,20 @@ +$onEmpty + +Set i(*) |_____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________| / +'seattle' |_____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________|, +'san-diego' |_____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________| /; + +Set j(*) |_____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________| / +'new-york' |_____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________|, +'chicago' |_____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________|, +'topeka' |_____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________| /; + +Parameter d(i,j) |_____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________| / +'seattle'.'new-york' 2.5, +'seattle'.'chicago' 1.7, +'seattle'.'topeka' 1.8, +'san-diego'.'new-york' 2.5, +'san-diego'.'chicago' 1.8, +'san-diego'.'topeka' 1.4 /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/small_example.txt b/src/tools/tests/output/gdxdump/small_example.txt new file mode 100644 index 0000000..60f8ed7 --- /dev/null +++ b/src/tools/tests/output/gdxdump/small_example.txt @@ -0,0 +1,20 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' 2.5, +'seattle'.'chicago' 1.7, +'seattle'.'topeka' 1.8, +'san-diego'.'new-york' 2.5, +'san-diego'.'chicago' 1.8, +'san-diego'.'topeka' 1.4 /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_epsilon.txt b/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_epsilon.txt new file mode 100644 index 0000000..fa564e7 --- /dev/null +++ b/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_epsilon.txt @@ -0,0 +1,20 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' Test, +'seattle'.'chicago' NA, +'seattle'.'topeka' +Inf, +'san-diego'.'new-york' -Inf, +'san-diego'.'chicago' Undf, +'san-diego'.'topeka' 0 /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_negative_infinity.txt b/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_negative_infinity.txt new file mode 100644 index 0000000..d827ac7 --- /dev/null +++ b/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_negative_infinity.txt @@ -0,0 +1,20 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' Eps, +'seattle'.'chicago' NA, +'seattle'.'topeka' +Inf, +'san-diego'.'new-york' Test, +'san-diego'.'chicago' Undf, +'san-diego'.'topeka' 0 /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_not_available.txt b/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_not_available.txt new file mode 100644 index 0000000..5044ee3 --- /dev/null +++ b/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_not_available.txt @@ -0,0 +1,20 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' Eps, +'seattle'.'chicago' Test, +'seattle'.'topeka' +Inf, +'san-diego'.'new-york' -Inf, +'san-diego'.'chicago' Undf, +'san-diego'.'topeka' 0 /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_positive_infinity.txt b/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_positive_infinity.txt new file mode 100644 index 0000000..4d902af --- /dev/null +++ b/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_positive_infinity.txt @@ -0,0 +1,20 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' Eps, +'seattle'.'chicago' NA, +'seattle'.'topeka' Test, +'san-diego'.'new-york' -Inf, +'san-diego'.'chicago' Undf, +'san-diego'.'topeka' 0 /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_undefined.txt b/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_undefined.txt new file mode 100644 index 0000000..bc1c25b --- /dev/null +++ b/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_undefined.txt @@ -0,0 +1,20 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' Eps, +'seattle'.'chicago' NA, +'seattle'.'topeka' +Inf, +'san-diego'.'new-york' -Inf, +'san-diego'.'chicago' Test, +'san-diego'.'topeka' 0 /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_zero.txt b/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_zero.txt new file mode 100644 index 0000000..211932b --- /dev/null +++ b/src/tools/tests/output/gdxdump/special_values_example_filter_default_values_out_zero.txt @@ -0,0 +1,20 @@ +$onEmpty + +Set i(*) supply / +'seattle', +'san-diego' /; + +Set j(*) markets / +'new-york', +'chicago', +'topeka' /; + +Parameter d(i,j) distance in thousands of miles / +'seattle'.'new-york' Eps, +'seattle'.'chicago' NA, +'seattle'.'topeka' +Inf, +'san-diego'.'new-york' -Inf, +'san-diego'.'chicago' Undf, +'san-diego'.'topeka' Test /; + +$offEmpty diff --git a/src/tools/tests/output/gdxdump/usage.txt b/src/tools/tests/output/gdxdump/usage.txt new file mode 100644 index 0000000..61f11e4 --- /dev/null +++ b/src/tools/tests/output/gdxdump/usage.txt @@ -0,0 +1,35 @@ +gdxdump: Write GDX file in ASCII +GDXDUMP 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS + +Usage: +gdxdump + + -V or -Version Write version info of input file only + Output= Write output to file + Symb= Select a single identifier + UelTable= Include all unique elements + Delim=[period, comma, tab, blank, semicolon] + Specify a dimension delimiter + DecimalSep=[period, comma] + Specify a decimal separator + NoHeader Suppress writing of the headers + NoData Write headers only; no data + CSVAllFields When writing CSV write all variable/equation fields + CSVSetText When writing CSV write set element text + Symbols Get a list of all symbols + DomainInfo Get a list of all symbols showing domain information + SymbolsAsSet Get a list of all symbols as data for a set + SymbolsAsSetDI Get a list of all symbols as data for a set includes domain information + SetText Show the list of set text (aka associated text) + Format=[normal, gamsbas, csv] + dFormat=[normal, hexponential, hexBytes] + CDim=[Y, N] Use last dimension as column headers + (for CSV format only; default=N) + FilterDef=[Y, N] Filter default values; default=Y + EpsOut= String to be used when writing the value for EPS; default=EPS + NaOut= String to be used when writing the value for Not Available; default=NA + PinfOut= String to be used when writing the value for Positive Infinity; default=+Inf + MinfOut= String to be used when writing the value for Negative Infinity; default=-Inf + UndfOut= String to be used when writing the value for Undefined; default=Undf + ZeroOut= String to be used when writing the value for Zero; default=0 + Header= New header for CSV output format diff --git a/src/tools/tests/output/gdxmerge/small_example_and_full_example.txt b/src/tools/tests/output/gdxmerge/small_example_and_full_example.txt new file mode 100644 index 0000000..9686b79 --- /dev/null +++ b/src/tools/tests/output/gdxmerge/small_example_and_full_example.txt @@ -0,0 +1,5 @@ +Output file: merged.gdx +Reading file: small_example.gdx +Reading file: full_example.gdx + +Merge complete, 2 input files merged diff --git a/src/tools/tests/output/gdxmerge/small_example_and_full_example_big_symbols.txt b/src/tools/tests/output/gdxmerge/small_example_and_full_example_big_symbols.txt new file mode 100644 index 0000000..63599b4 --- /dev/null +++ b/src/tools/tests/output/gdxmerge/small_example_and_full_example_big_symbols.txt @@ -0,0 +1,13 @@ +Output file: merged.gdx +Reading file: small_example.gdx +Reading file: full_example.gdx +Reading file: small_example.gdx +Reading file: full_example.gdx + +looking for symbol j +looking for symbol d +looking for symbol c +looking for symbol x +looking for symbol supply +looking for symbol demand +Merge complete, 4 input files merged diff --git a/src/tools/tests/output/gdxmerge/small_example_and_full_example_exclude_i.txt b/src/tools/tests/output/gdxmerge/small_example_and_full_example_exclude_i.txt new file mode 100644 index 0000000..a159641 --- /dev/null +++ b/src/tools/tests/output/gdxmerge/small_example_and_full_example_exclude_i.txt @@ -0,0 +1,6 @@ +Exclude Id: i +Output file: merged.gdx +Reading file: small_example.gdx +Reading file: full_example.gdx + +Merge complete, 2 input files merged diff --git a/src/tools/tests/output/gdxmerge/small_example_and_full_example_exclude_i_j.txt b/src/tools/tests/output/gdxmerge/small_example_and_full_example_exclude_i_j.txt new file mode 100644 index 0000000..e5d4c29 --- /dev/null +++ b/src/tools/tests/output/gdxmerge/small_example_and_full_example_exclude_i_j.txt @@ -0,0 +1,7 @@ +Exclude Id: i +Exclude Id: j +Output file: merged.gdx +Reading file: small_example.gdx +Reading file: full_example.gdx + +Merge complete, 2 input files merged diff --git a/src/tools/tests/output/gdxmerge/small_example_and_full_example_id_i.txt b/src/tools/tests/output/gdxmerge/small_example_and_full_example_id_i.txt new file mode 100644 index 0000000..7146f5b --- /dev/null +++ b/src/tools/tests/output/gdxmerge/small_example_and_full_example_id_i.txt @@ -0,0 +1,6 @@ +Include Id: i +Output file: merged.gdx +Reading file: small_example.gdx +Reading file: full_example.gdx + +Merge complete, 2 input files merged diff --git a/src/tools/tests/output/gdxmerge/small_example_and_full_example_id_i_j.txt b/src/tools/tests/output/gdxmerge/small_example_and_full_example_id_i_j.txt new file mode 100644 index 0000000..61ef27c --- /dev/null +++ b/src/tools/tests/output/gdxmerge/small_example_and_full_example_id_i_j.txt @@ -0,0 +1,7 @@ +Include Id: i +Include Id: j +Output file: merged.gdx +Reading file: small_example.gdx +Reading file: full_example.gdx + +Merge complete, 2 input files merged diff --git a/src/tools/tests/output/gdxmerge/small_example_and_small_example_changed_data_and_full_example.txt b/src/tools/tests/output/gdxmerge/small_example_and_small_example_changed_data_and_full_example.txt new file mode 100644 index 0000000..4bb5d05 --- /dev/null +++ b/src/tools/tests/output/gdxmerge/small_example_and_small_example_changed_data_and_full_example.txt @@ -0,0 +1,6 @@ +Output file: merged.gdx +Reading file: small_example.gdx +Reading file: small_example_changed_data.gdx +Reading file: full_example.gdx + +Merge complete, 3 input files merged diff --git a/src/tools/tests/output/gdxmerge/usage.txt b/src/tools/tests/output/gdxmerge/usage.txt new file mode 100644 index 0000000..b189b22 --- /dev/null +++ b/src/tools/tests/output/gdxmerge/usage.txt @@ -0,0 +1,13 @@ +gdxmerge: Merge GDX files +GDXMERGE 47.4.1 4b675771 Aug 13, 2024 DAC arm 64bit/macOS + +Usage: + gdxmerge filepat1 filepat2 ... filepatn + Optional parameters: + id=ident1, ident2: Merge specified IDs only + exclude=ident1, ident2: Do not merge specified IDs + big= : Size indicator for a large symbol + output=filename : Output file; merged.gdx by default + strict=true/false: Terminate on failure, e.g. missing input files +Filepat represents a filename or a file pattern. +The form: @filename will process parameters from that file diff --git a/targets.cmake b/targets.cmake index 08eb02a..a8eecac 100644 --- a/targets.cmake +++ b/targets.cmake @@ -93,4 +93,51 @@ if (DEFINED ENV{GAMSDIR}) endif () endif () -endif() \ No newline at end of file +endif() + +set(NO_TOOLS OFF CACHE BOOL "Skip building GDX tools") +if(NOT NO_TOOLS) + +# Library for gdxdump, gdxdiff and gdxmerge +add_library(gdxtools-library + generated/gdxcc.h + generated/gdxcc.c + src/tools/library/common.h + src/tools/library/common.cpp + src/tools/library/short_string.h + src/tools/library/short_string.cpp + src/tools/library/cmdpar.h + src/tools/library/cmdpar.cpp + ${base-units-all} +) +target_include_directories(gdxtools-library PRIVATE ${inc-dirs}) +target_link_libraries(gdxtools-library gdx-static) +if (UNIX) + target_link_libraries(gdxtools-library dl) +endif () + +# gdxdump +add_executable(gdxdump + src/tools/gdxdump/gdxdump.h + src/tools/gdxdump/gdxdump.cpp +) +target_include_directories(gdxdump PRIVATE ${inc-dirs}) +target_link_libraries(gdxdump gdxtools-library) + +# gdxdiff +add_executable(gdxdiff + src/tools/gdxdiff/gdxdiff.h + src/tools/gdxdiff/gdxdiff.cpp +) +target_include_directories(gdxdiff PRIVATE ${inc-dirs}) +target_link_libraries(gdxdiff gdxtools-library) + +# gdxmerge +add_executable(gdxmerge + src/tools/gdxmerge/gdxmerge.h + src/tools/gdxmerge/gdxmerge.cpp +) +target_include_directories(gdxmerge PRIVATE ${inc-dirs}) +target_link_libraries(gdxmerge gdxtools-library) + +endif(NOT NO_TOOLS)