gdxperf.gms : Test various GDX APIs and report run times

Description

Without any options this produces a GDX file from trnsport and exercises all GDX API.
With --RUN=single --SINGLERUN=name.gdx this will do the same for any given GDX file.
With --RUNDEFAULT=0 one can turn off all API test and selectively turn them on with
--RUNXYZ=1 (with XYZ being GAMS, G2NP, GTP, GMD, OOAPI, GDX, CTYPES, and CAPI).
The CAPI requires a C compiler to be installed. If the shared library can't be build,
this API test is skipped even if requested.

The model can also work with subdirctories containing many GDX files. For this set
--RUN=suite and --SUITE=dirname or --SUITE=ALL to run a predefined set of suites:
  - lwsup
  - mrb
  - sqagams
  - src

The timing information can we written to the GAMS log (default), stdout (--LOGFILE=stdout)
or any file (--LOGFILE=name.txt).

Contributor: Michael Bussieck, December 2022


Small Model of Type : Python


Category : GAMS API library


Main file : gdxperf.gms

$TITLE 'Test various GDX APIs and report run times' (gdxperf,SEQ=62)

$ontext
Without any options this produces a GDX file from trnsport and exercises all GDX API. 
With --RUN=single --SINGLERUN=name.gdx this will do the same for any given GDX file.
With --RUNDEFAULT=0 one can turn off all API test and selectively turn them on with 
--RUNXYZ=1 (with XYZ being GAMS, G2NP, GTP, GMD, OOAPI, GDX, CTYPES, and CAPI).
The CAPI requires a C compiler to be installed. If the shared library can't be build, 
this API test is skipped even if requested.

The model can also work with subdirctories containing many GDX files. For this set
--RUN=suite and --SUITE=dirname or --SUITE=ALL to run a predefined set of suites:
  - lwsup
  - mrb
  - sqagams
  - src

The timing information can we written to the GAMS log (default), stdout (--LOGFILE=stdout)
or any file (--LOGFILE=name.txt).

Contributor: Michael Bussieck, December 2022
$offtext


$if not set RUN $set RUN ELSE_CASE
$ifThenI %RUN%==SINGLE
$if not set SINGLERUN $set SINGLERUN ./sqagams/ELMOD.gdx
set fgdx / '%SINGLERUN%' /;

$elseIfI %RUN%==SUITE
set suites /
             lwproj  "Collection of 499 GDX found on Lutz Westermanns drive related to projects"
             lwsup   "Collection of 303 GDX found on Lutz Westermanns drive resulting from [personal] support"
             mrb     "Collection of  73 GDX found on Michael Bussiecks drive (often resulting from [personal] support)"
             sqagams "Collection of 534 GDX Files from sqagams excluding gamsmatr*.gdx"
             src     "Collection of 177 GDX found in products/src"
           /;
$onEmbedded
set drop(suites,*) /
          lwproj.(('rank_out.gdx','gmsgrid.gdx')                         "https://git.gams.com/devel/products/-/issues/5412"
                   'validnlpecopts.gdx'                                  "https://git.gams.com/devel/products/-/issues/5413"
                   'sol.gdx'                                             "https://git.gams.com/devel/products/-/issues/5419"
                  ('Test_Leitung2_out.gdx', 'Test_Leitung2_visual.gdx',
                   'Test_Leitung_out.gdx', 'Test_Leitung_visual.gdx')    "https://git.gams.com/devel/products/-/issues/5440"  )
           lwsup.(('5062.gdx', 'data1000.gdx', 'data10000.gdx'
                   'Modellendogen0004.gdx', 'Modellendogen0005.gdx', 'Modellendogen0725.gdx', 'Modellendogen0726.gdx', 'Modellendogen1093.gdx', 'Modellendogen1407.gdx'
                   'output2.gdx', 'output_DisEmp.gdx'
                   'preFix.gdx'
                   'soln_Toroptimierung_2_p1.gdx', 'Toroptimierung_2_p.gdx', 'Toroptimierung_2_p - Copy.gdx'
                   'z.gdx')                                                    "https://git.gams.com/devel/products/-/issues/5440"
                   'data_m5.gdx'                                               "https://git.gams.com/devel/products/-/issues/5419"
                  ('dbX2370.gdx', 'dbX2372.gdx')                               "https://git.gams.com/devel/products/-/issues/5439"
                   'gmsgrid.gdx'                                               "https://git.gams.com/devel/products/-/issues/5412"
                  ('testInstance1000Ite.gdx', 'testInstance100Ite.gdx', 'testInstance200Ite.gdx'
                   'x0.gdx','x1.gdx','x170.gdx','x170postfix.gdx','x171.gdx')  "https://git.gams.com/devel/products/-/issues/5441")
             mrb.('test.gdx','gdxdump.gdx',
                  'map.gdx', 'new4.gdx'             "https://git.gams.com/devel/products/-/issues/5419"
                  'food_man.gdx'                    "https://git.gams.com/devel/gams-transfer-python/-/issues/26"
                  'testExcel.gdx'                   "big, requires 25GB for g2np")
         sqagams.('ELMOD.gdx','JMM.gdx',
                  'arauco.gdx',
                  'modelo.gdx',
                  'PERSEUS.gdx'                     "big, requires 25GB for g2np"
                  'AssignmentBug.gdx','globiom.gdx',
                  'indata.gdx', 'seders.gdx'        "https://git.gams.com/devel/products/-/issues/5413"
                  'CHPSystemData2_Converted.gdx'    "https://git.gams.com/devel/gams-transfer-python/-/issues/27"
                  'pegase.gdx'                      "large scalar model, a killer for gtp")
             src.('getdata.gdx'                     "https://git.gams.com/devel/gams-transfer-python/-/issues/26"
                  'rank_out.gdx', 'rank_output.gdx',
                  'bau_p.gdx'                       "https://git.gams.com/devel/products/-/issues/5412"
                  'validnlpecopts.gdx','xyz.gdx'    "https://git.gams.com/devel/products/-/issues/5413")
           /;
$offEmbedded
$if not set SUITE $set SUITE ALL
set suite(*) /
$ifi not x%SUITE%==xALL %SUITE%
$ifi     x%SUITE%==xALL #suites
/;

set fgdx(*) "GDX files to run";
$onEmbeddedCode Python:
import glob
import os
f = []
suite = list(gams.get('suite'))
for s in suite:
   f = f + glob.glob(f'./{s}/*.gdx')
for d in gams.get('drop'):
   if d[0] in suite:
      # print(f'./{d[0]}{os.sep}{d[1]}')
      f.remove(f'./{d[0]}{os.sep}{d[1]}')

# Use the code to skip all models in the list up and including the model given by the name
#for i in range(f.index('./lwproj\\Test_Leitung2_out.gdx')+1): f.pop(0)
gams.set('fgdx',f)
$offEmbeddedCode fgdx

$elseIfI %RUN%==HARDCODE
* Some hand coded GDX file list
$inlineCom /* */
set fgdx / '1.gdx', '2.gdx', '3.gdx' /
$else
* Get trnsport from model lib and run that
$call gamslib -q trnsport
$call gams trnsport a=c lo=0 gdx=trnsport.gdx
set fgdx / 'trnsport.gdx' /
$endif

$if not set LOGFILE    $set LOGFILE    GAMSLOG
$if not set RUNDEFAULT $set RUNDEFAULT 1
$if not set RUNGAMS    $set RUNGAMS    %RUNDEFAULT%
$if not set RUNG2NP    $set RUNG2NP    %RUNDEFAULT%
$if not set RUNGTP     $set RUNGTP     %RUNDEFAULT%
$if not set RUNGMD     $set RUNGMD     %RUNDEFAULT%
$if not set RUNOOAPI   $set RUNOOAPI   %RUNDEFAULT%
$if not set RUNGDX     $set RUNGDX     %RUNDEFAULT%
$if not set RUNCTYPES  $set RUNCTYPES  %RUNDEFAULT%
$if not set RUNCAPI    $set RUNCAPI    %RUNDEFAULT%

$onEmbeddedCode Python:
import time
import os
import sys
from gams.core.numpy import Gams2Numpy
import gams.transfer as gt
from gams.core.gdx import *
from gams.core.gmd import *
import numpy as np

def register_uels(gdx, uel_list, reverse=False):
   if reverse:
      uels = reversed(list(enumerate(uel_list[1:])))
   else:
      uels = list(enumerate(uel_list[1:]))

   rc = gdxUELRegisterMapStart(gdx)
   assert rc == 1
   for unr, u in uels:
       rc = gdxUELRegisterMap(gdx, unr+1, u)
       assert rc == 1
   rc = gdxUELRegisterDone(gdx)

step_cnt = 0
lastfn = ''
lastapi = ''

def printT(f, t, fn, api, text):
   global lastfn, lastapi, step_cnt 
   item_t = time.time() - t[2]
   total_t = t[0] + item_t 
   subtotal_t = t[1] + item_t
   if lastapi == api and fn == lastfn:
      step_cnt += 1
   else: 
      step_cnt = 0
      lastfn = fn
      lastapi = api
   if f == None:
       gams.printLog(f'{total_t:9.3f} # {subtotal_t:9.3f} # {item_t:9.3f} # {fn:10s} # {api} # {step_cnt} # {text}')
   else:
       f.write(f'{total_t:9.3f} # {subtotal_t:9.3f} # {item_t:9.3f} # {fn:10s} # {api} # {step_cnt} # {text}\n')
       f.flush()
   return [total_t, subtotal_t, time.time()]
t = [0,0,0]

if '%LOGFILE%' == 'stdout':
   flog = sys.stdout
elif '%LOGFILE%' == 'GAMSLOG':
   flog = None
else:
   flog = open('%LOGFILE%', 'w')
$offEmbeddedCode

* GAMS Test
$ifThen %RUNGAMS% == 1
$onEmbeddedCode Python:
for f in gams.get('fgdx'):
   t[1] = 0
   t[2] = time.time()
   with open('gdxdump.gms', "w") as fp:
      fp.write('$onUNDF\n')
   rc = os.system(f'gdxdump "{f}" noData >> gdxdump.gms')
   t = printT(flog, t, f, 'gams', 'gdxdump')
   assert rc == 0
   rc = os.system(f'gams gdxdump.gms lo=0')
   t = printT(flog, t, f, 'gams', 'read data - $load - gdxDataReadFilteredStart/gdxDataReadMap')
   assert rc == 0
   os.system(f'gams gdxdump.gms lo=0 gdx=default')
   t = printT(flog, t, f, 'gams', 'read and write data - dumpGDX - gdxDataWriteRaw')
   assert rc == 0
$offEmbeddedCode
$endIf

* GAMS2Numpy Test
$ifThen %RUNG2NP% == 1
$onEmbeddedCode Python:
import copy

system_directory = r'%gams.sysdir% '.rstrip()
g2np = Gams2Numpy(system_directory)
gdxHandle = new_gdxHandle_tp()
rc, msg = gdxCreateD(gdxHandle, system_directory, GMS_SSSIZE)
if not rc:
    raise Exception(msg)
wgdxHandle = new_gdxHandle_tp()
rc, msg = gdxCreateD(wgdxHandle, system_directory, GMS_SSSIZE)
if not rc:
    raise Exception(msg)

for f in gams.get('fgdx'):
   t[1] = 0
   t[2] = time.time()
   rc = gdxOpenRead(gdxHandle, f'{f}')[0]
   t = printT(flog, t, f, 'g2np', 'gdxOpenRead')
   assert rc == 1
   rc, symCount, _ = gdxSystemInfo(gdxHandle)
   t = printT(flog, t, f, 'g2np', f'handling {symCount} symbols')
   assert rc == 1

   for i in range(1, symCount + 1):
       rc, syid, sydim, sytype = gdxSymbolInfo(gdxHandle, i)
       if sytype == GMS_DT_ALIAS:
          continue
       keys, values = g2np.gdxReadSymbolRaw(gdxHandle, syid)
   t = printT(flog, t, f, 'g2np', 'reading symbols raw')

   uel_list = g2np.gdxGetUelList(gdxHandle)
   for i in range(1, symCount + 1):
       rc, syid, sydim, sytype = gdxSymbolInfo(gdxHandle, i)
       if sytype == GMS_DT_ALIAS:
          continue
       keys, values = g2np.gdxReadSymbolStr(gdxHandle, syid, uelList=uel_list)
   t = printT(flog, t, f, 'g2np', 'reading symbols str with uel list')

   for i in range(1, symCount + 1):
       rc, syid, sydim, sytype = gdxSymbolInfo(gdxHandle, i)
       if sytype == GMS_DT_ALIAS:
          continue
       keys, values = g2np.gdxReadSymbolStr(gdxHandle, syid)
   t = printT(flog, t, f, 'g2np', 'reading symbols str without uel list')

   # This GDX file won't have uels
   rc = gdxOpenWrite(wgdxHandle, "out.gdx", "g2np test")[0]
   for i in range(1, symCount + 1):
       rc, syid, sydim, sytype = gdxSymbolInfo(gdxHandle, i)
       if sytype == GMS_DT_ALIAS:
          continue
       keys, values = g2np.gdxReadSymbolRaw(gdxHandle, syid)
       g2np.gdxWriteSymbolRaw(wgdxHandle, syid, '', sydim, sytype, 0, keys, values)
   rc = gdxClose(wgdxHandle)
   t = printT(flog, t, f, 'g2np', 'reading and writing symbols raw')
   assert rc == 0

   rc = gdxOpenWrite(wgdxHandle, "out.gdx", "g2np test")[0]
   register_uels(wgdxHandle, uel_list, reverse=False)
   t = printT(flog, t, f, 'g2np', 'register uels (reverse=False)')

   for i in range(1, symCount + 1):
       rc, syid, sydim, sytype = gdxSymbolInfo(gdxHandle, i)
       if sytype == GMS_DT_ALIAS:
          continue
       keys, values = g2np.gdxReadSymbolStr(gdxHandle, syid, uelList=uel_list)
       g2np.gdxWriteSymbolStr(wgdxHandle, syid, '', sydim, sytype, 0, keys, values)
   rc = gdxClose(wgdxHandle)
   t = printT(flog, t, f, 'g2np', 'reading and writing symbols str reverse=False')
   assert rc == 0

   rc = gdxOpenWrite(wgdxHandle, "out.gdx", "g2np test")[0]
   register_uels(wgdxHandle, uel_list, reverse=True)
   t = printT(flog, t, f, 'g2np', 'register uels (reverse=True)')

   for i in range(1, symCount + 1):
       rc, syid, sydim, sytype = gdxSymbolInfo(gdxHandle, i)
       if sytype == GMS_DT_ALIAS:
          continue
       keys, values = g2np.gdxReadSymbolStr(gdxHandle, syid, uelList=uel_list)
       g2np.gdxWriteSymbolStr(wgdxHandle, syid, '', sydim, sytype, 0, keys, values)
   rc = gdxClose(wgdxHandle)
   t = printT(flog, t, f, 'g2np', 'reading and writing symbols str reverse=True')
   assert rc == 0

   rc = gdxClose(gdxHandle)
   assert rc == 0

gdxFree(wgdxHandle)
gdxFree(gdxHandle)
$offEmbeddedCode
$endIf

* GAMS Transfer Python Test
$ifThen %RUNGTP% == 1
$onEmbeddedCode Python:
import warnings
warnings.filterwarnings("ignore", category=UserWarning)

for f in gams.get('fgdx'):
   t[1] = 0
   t[2] = time.time() 
   m = gt.Container(f, system_directory=r"%gams.sysdir% ".strip())
   t = printT(flog, t, f, 'gtp', 'constructor with reading')

   m.write('out.gdx')
   t = printT(flog, t, f, 'gtp', 'write')
   
   # roll the last to the first to create out of order writing
   for sn,s in m.data.items():
      if not isinstance(s,gt.Alias) and not isinstance(s,gt.UniverseAlias) and s.number_records > 1:
         s.setRecords(s.records.apply(np.roll, shift=1))
   t = printT(flog, t, f, 'gtp', 'create out-of-order dataframes')
      
   m.write('out.gdx')
   t = printT(flog, t, f, 'gtp', 'write out-of-order')
$offEmbeddedCode
$endIf

* GMD Test
$ifThen %RUNGMD% == 1
$onEmbeddedCode Python:
system_directory = r'%gams.sysdir% '.rstrip()

for f in gams.get('fgdx'):
   t[1] = 0
   t[2] = time.time()
   gmdHandle = new_gmdHandle_tp()
   rc, msg = gmdCreateD(gmdHandle, system_directory, GMS_SSSIZE)
   if not rc:
       raise Exception(msg)
   rc = gmdInitFromGDX(gmdHandle, f'{f}')
   t = printT(flog, t, f, 'gmd', 'gmdInitFromGDX')
   assert rc == 1

   rc = gmdCloseGDX(gmdHandle, True)
   t = printT(flog, t, f, 'gmd', 'gmdCloseGDX')
   assert rc == 1

   rc = gmdWriteGDX(gmdHandle, 'out.gdx', False)
   t = printT(flog, t, f, 'gmd', 'gmdWriteGDX with domain checking')
   assert rc == 1

   rc = gmdWriteGDX(gmdHandle, 'out.gdx', True)
   t = printT(flog, t, f, 'gmd', 'gmdWriteGDX without domain checking')
   assert rc == 1

   gmdFree(gmdHandle)
$offEmbeddedCode
$endIf

* GAMS OO API
$ifThen %RUNOOAPI% == 1
$onEmbeddedCode Python:
from gams import *
system_directory = r'%gams.sysdir% '.rstrip()
ws = GamsWorkspace(system_directory=system_directory, working_directory='./')
for f in gams.get('fgdx'):
   t[1] = 0
   t[2] = time.time()
   db = ws.add_database_from_gdx(f'{f}')
   t = printT(flog, t, f, 'ooapi', 'ws.add_database_from_gdx')

   rc = gmdCloseGDX(db._gmd, True)
   t = printT(flog, t, f, 'ooapi', 'gmdCloseGDX')
   assert rc == 1

   db.export('out.gdx')
   t = printT(flog, t, f, 'ooapi', 'db.export with domain checking')

   db.suppress_auto_domain_checking = True
   db.export('out.gdx')
   t = printT(flog, t, f, 'ooapi', 'db.export without domain checking')
$offEmbeddedCode
$endIf

* GAMS GDX Python Test
$ifThen %RUNGDX% == 1
$onEmbeddedCode Python:
system_directory = r'%gams.sysdir% '.rstrip()
gdxHandle = new_gdxHandle_tp()
rc, msg = gdxCreateD(gdxHandle, system_directory, GMS_SSSIZE)
if not rc:
    raise Exception(msg)
wgdxHandle = new_gdxHandle_tp()
rc, msg = gdxCreateD(wgdxHandle, system_directory, GMS_SSSIZE)
if not rc:
    raise Exception(msg)
g2np = Gams2Numpy(system_directory)

for f in gams.get('fgdx'):
   t[1] = 0
   t[2] = time.time() 
   rc = gdxOpenRead(gdxHandle, f'{f}')[0]
   t = printT(flog, t, f, 'gdx', 'gdxOpenRead')
   assert rc == 1

   rc, symCount, _ = gdxSystemInfo(gdxHandle)
   t = printT(flog, t, f, 'gdx', f'handling {symCount} symbols')
   assert rc == 1

   for i in range(1, symCount + 1):
       rc, syid, sydim, sytype = gdxSymbolInfo(gdxHandle, i)
       rc, nrecs =  gdxDataReadRawStart(gdxHandle, i)
       assert rc == 1
       for k in range(nrecs):
           rc, elements, values, afdim = gdxDataReadRaw(gdxHandle)
           assert rc == 1
       gdxDataReadDone(gdxHandle)
   t = printT(flog, t, f, 'gdx', 'reading symbols raw')

   for i in range(1, symCount + 1):
       rc, syid, sydim, sytype = gdxSymbolInfo(gdxHandle, i)
       rc, nrecs =  gdxDataReadStrStart(gdxHandle, i)
       assert rc == 1
       for k in range(nrecs):
           rc, elements, values, afdim = gdxDataReadStr(gdxHandle)
           assert rc == 1
       gdxDataReadDone(gdxHandle)
   t = printT(flog, t, f, 'gdx', 'reading symbols str')
   
   uel_list = g2np.gdxGetUelList(gdxHandle)
   register_uels(gdxHandle, uel_list, reverse=False)
   t = printT(flog, t, f, 'gdx', 'register uels (reverse=False)')

   for i in range(1, symCount + 1):
       rc, syid, sydim, sytype = gdxSymbolInfo(gdxHandle, i)
       rc, nrecs =  gdxDataReadMapStart(gdxHandle, i)
       assert rc == 1
       for k in range(nrecs):
           rc, elements, values, afdim = gdxDataReadMap(gdxHandle,k+1)
           assert rc == 1
       gdxDataReadDone(gdxHandle)
   t = printT(flog, t, f, 'gdx', 'reading symbols map reverse=False')
   rc = gdxClose(gdxHandle)
   assert rc == 0

   rc = gdxOpenRead(gdxHandle, f'{f}')[0]
   t = printT(flog, t, f, 'gdx', 'gdxOpenRead')
   assert rc == 1
   
   register_uels(gdxHandle, uel_list, reverse=True)
   t = printT(flog, t, f, 'gdx', 'register uels (reverse=True)')

   for i in range(1, symCount + 1):
       rc, syid, sydim, sytype = gdxSymbolInfo(gdxHandle, i)
       rc, nrecs =  gdxDataReadMapStart(gdxHandle, i)
       assert rc == 1
       for k in range(nrecs):
           rc, elements, values, afdim = gdxDataReadMap(gdxHandle,k+1)
           assert rc == 1
       gdxDataReadDone(gdxHandle)
   t = printT(flog, t, f, 'gdx', 'reading symbols map reverse=True')

   rc = gdxOpenWrite(wgdxHandle, "out.gdx", "gdx test")[0]
   assert rc == 1
   for i in range(1, symCount + 1):
       rc, syid, sydim, sytype = gdxSymbolInfo(gdxHandle, i)
       assert rc == 1
       if sytype == GMS_DT_ALIAS:
          continue
       rc = gdxDataWriteRawStart(wgdxHandle, syid, "", sydim, sytype , 0)
       assert rc == 1
       rc, nrecs =  gdxDataReadRawStart(gdxHandle, i)
       assert rc == 1
       keys = intArray(sydim)
       vals = doubleArray(5)
       for j in range(nrecs):
           rc, elements, values, afdim = gdxDataReadRaw(gdxHandle)
           assert rc == 1
           # The copy should not be necessary!
           for k,v in enumerate(elements):
               keys[k] = v
           for k,v in enumerate(values):
               vals[k] = v
           rc = gdxDataWriteRaw(wgdxHandle, keys, vals)
           assert rc == 1
       gdxDataWriteDone(wgdxHandle)
       gdxDataReadDone(gdxHandle)
   rc = gdxClose(wgdxHandle)
   t = printT(flog, t, f, 'gdx', 'reading and writing symbols raw')
   assert rc == 0

   rc = gdxOpenWrite(wgdxHandle, "out.gdx", "gdx test")[0]
   register_uels(wgdxHandle, uel_list, reverse=False)
   t = printT(flog, t, f, 'gdx', 'register uels (reverse=False)')
   assert rc == 1

   for i in range(1, symCount + 1):
       rc, syid, sydim, sytype = gdxSymbolInfo(gdxHandle, i)
       assert rc == 1
       if sytype == GMS_DT_ALIAS:
          continue
       rc = gdxDataWriteMapStart(wgdxHandle, syid, "", sydim, sytype , 0)
       assert rc == 1
       rc, nrecs =  gdxDataReadRawStart(gdxHandle, i)
       assert rc == 1
       keys = intArray(sydim)
       vals = doubleArray(5)
       for j in range(nrecs):
           rc, elements, values, afdim = gdxDataReadRaw(gdxHandle)
           assert rc == 1
           # The copy should not be necessary!
           for k,v in enumerate(elements):
               keys[k] = v
           for k,v in enumerate(values):
               vals[k] = v
           rc = gdxDataWriteMap(wgdxHandle, keys, vals)
           assert rc == 1
       gdxDataWriteDone(wgdxHandle)
       gdxDataReadDone(gdxHandle)
   rc = gdxClose(wgdxHandle)
   t = printT(flog, t, f, 'gdx', 'reading raw and writing symbols map - reverse=False')
   assert rc == 0

   rc = gdxOpenWrite(wgdxHandle, "out.gdx", "gdx test")[0]
   register_uels(wgdxHandle, uel_list, reverse=False)
   t = printT(flog, t, f, 'gdx', 'register uels (reverse=True)')
   assert rc == 1

   for i in range(1, symCount + 1):
       rc, syid, sydim, sytype = gdxSymbolInfo(gdxHandle, i)
       assert rc == 1
       if sytype == GMS_DT_ALIAS:
          continue
       rc = gdxDataWriteMapStart(wgdxHandle, syid, "", sydim, sytype , 0)
       assert rc == 1
       rc, nrecs =  gdxDataReadRawStart(gdxHandle, i)
       assert rc == 1
       keys = intArray(sydim)
       vals = doubleArray(5)
       for j in range(nrecs):
           rc, elements, values, afdim = gdxDataReadRaw(gdxHandle)
           assert rc == 1
           # The copy should not be necessary!
           for k,v in enumerate(elements):
               keys[k] = v
           for k,v in enumerate(values):
               vals[k] = v
           rc = gdxDataWriteMap(wgdxHandle, keys, vals)
           assert rc == 1
       gdxDataWriteDone(wgdxHandle)
       gdxDataReadDone(gdxHandle)
   rc = gdxClose(wgdxHandle)
   t = printT(flog, t, f, 'gdx', 'reading raw and writing symbols map - reverse=True')
   assert rc == 0

   rc = gdxOpenWrite(wgdxHandle, "out.gdx", "gdx test")[0]
   register_uels(wgdxHandle, uel_list, reverse=False)
   t = printT(flog, t, f, 'gdx', 'register uels (reverse=False)')
   assert rc == 1

   for i in range(1, symCount + 1):
       rc, syid, sydim, sytype = gdxSymbolInfo(gdxHandle, i)
       assert rc == 1
       if sytype == GMS_DT_ALIAS:
          continue
       rc = gdxDataWriteStrStart(wgdxHandle, syid, "", sydim, sytype , 0)
       assert rc == 1
       rc, nrecs =  gdxDataReadStrStart(gdxHandle, i)
       assert rc == 1
       vals = doubleArray(5)
       for j in range(nrecs):
           rc, elements, values, afdim = gdxDataReadStr(gdxHandle)
           assert rc == 1
           # The copy should not be necessary!
           for k,v in enumerate(values):
               vals[k] = v
           rc = gdxDataWriteStr(wgdxHandle, elements, vals)
           assert rc == 1
       gdxDataWriteDone(wgdxHandle)
       gdxDataReadDone(gdxHandle)
   rc = gdxClose(wgdxHandle)
   t = printT(flog, t, f, 'gdx', 'reading and writing symbols str reverse-false')
   assert rc == 0

   rc = gdxOpenWrite(wgdxHandle, "out.gdx", "gdx test")[0]
   register_uels(wgdxHandle, uel_list, reverse=True)
   t = printT(flog, t, f, 'gdx', 'register uels (reverse=True)')
   assert rc == 1

   for i in range(1, symCount + 1):
       rc, syid, sydim, sytype = gdxSymbolInfo(gdxHandle, i)
       assert rc == 1
       if sytype == GMS_DT_ALIAS:
          continue
       rc = gdxDataWriteStrStart(wgdxHandle, syid, "", sydim, sytype , 0)
       assert rc == 1
       rc, nrecs =  gdxDataReadStrStart(gdxHandle, i)
       assert rc == 1
       vals = doubleArray(5)
       for j in range(nrecs):
           rc, elements, values, afdim = gdxDataReadStr(gdxHandle)
           assert rc == 1
           # The copy should not be necessary!
           for k,v in enumerate(values):
               vals[k] = v
           rc = gdxDataWriteStr(wgdxHandle, elements, vals)
       gdxDataWriteDone(wgdxHandle)
       gdxDataReadDone(gdxHandle)
   rc = gdxClose(wgdxHandle)
   t = printT(flog, t, f, 'gdx', 'reading and writing symbols str reverse-true')
   assert rc == 0

   rc = gdxClose(gdxHandle)
   assert rc == 0
   
$offEmbeddedCode
$endIf

* GAMS GDX Python Test
$ifThen %RUNCTYPES% == 1
$if not set GDXCCTYPE $set GDXCCTYPE cc
$ifThen.gdxccttype %GDXCCTYPE%==dc
$  set CGDX cgdx
$  set GDX gdx
$else.gdxccttype
$  set CGDX c__gdx
$  set GDX c__gdx
$endif.gdxccttype

$onEmbeddedCode Python:
from ctypes import *

system_directory = r'%gams.sysdir% '.rstrip()
if sys.platform == "linux" or sys.platform == "linux2":
    so_name = "libgdx%GDXCCTYPE%lib64.so"
elif sys.platform == "darwin":
    so_name = "libgdx%GDXCCTYPE%lib64.dylib"
elif sys.platform == "win32":
    so_name = "gdx%GDXCCTYPE%lib64.dll"
else:
    raise Exception(f'unknown OS {sys.platform}')
gdx = cdll.LoadLibrary(os.path.join(system_directory, so_name))

gdxHandle = c_void_p(0)
gdx.xcreate(byref(gdxHandle))
wgdxHandle = c_void_p(0)
gdx.xcreate(byref(wgdxHandle))
ival = c_int(0)
ival_p = byref(ival)
jval = c_int(0)
sval = create_string_buffer(256)
uels = [create_string_buffer(64) for i in range(20)]
uelsptr = (c_char_p*20)(*map(addressof, uels))
vals = (c_double*5)((0.0)*5)
vals_p = pointer(vals)
rawuels = (c_int*20)((0)*20)
rawuels_p = pointer(rawuels)

for f in gams.get('fgdx'):
   t[1] = 0
   t[2] = time.time()
   rc = gdx.%CGDX%openread(gdxHandle, c_char_p(f.encode()), byref(ival))
   t = printT(flog, t, f, 'ctypes', 'cgdxopenread')
   assert rc == 1

   rc = gdx.%GDX%systeminfo(gdxHandle, byref(ival), byref(jval))
   symCount = ival.value;
   t = printT(flog, t, f, 'ctypes', f'handling {symCount} symbols')
   assert rc == 1

   for i in range(1, symCount + 1):
       rc = gdx.%CGDX%symbolinfo(gdxHandle, c_int(i), sval, byref(ival), byref(jval))
       syid = sval.value
       sydim = ival.value
       sytype = jval.value
       assert rc == 1
       if sytype == 4: # GMS_DT_ALIAS
          continue
       rc = gdx.%GDX%datareadrawstart(gdxHandle, c_int(i), byref(ival))
       nrecs = ival.value
       assert rc == 1
       for j in range(nrecs):
           rc = gdx.%GDX%datareadraw(gdxHandle, rawuels_p, vals_p, ival_p)
           assert rc == 1
       rc = gdx.%GDX%datareaddone(gdxHandle)
   t = printT(flog, t, f, 'ctypes', 'reading symbols raw')

   for i in range(1, symCount + 1):
       rc = gdx.%CGDX%symbolinfo(gdxHandle, c_int(i), sval, byref(ival), byref(jval))
       syid = sval.value
       sydim = ival.value
       sytype = jval.value
       assert rc == 1
       if sytype == 4: # GMS_DT_ALIAS
          continue
       rc = gdx.%GDX%datareadstrstart(gdxHandle, c_int(i), byref(ival))
       nrecs = ival.value
       assert rc == 1
       for j in range(nrecs):
           rc = gdx.%CGDX%datareadstr(gdxHandle, uelsptr, vals_p, ival_p)
           assert rc == 1
       rc = gdx.%GDX%datareaddone(gdxHandle)
   t = printT(flog, t, f, 'ctypes', 'reading symbols str')

   rc = gdx.%CGDX%openwrite(wgdxHandle, c_char_p(b'out.gdx'), c_char_p(b'ctypes experiment'), byref(ival))
   t = printT(flog, t, f, 'ctypes', 'cgdxopenwrite')
   assert rc == 1

   for i in range(1, symCount + 1):
       rc = gdx.%CGDX%symbolinfo(gdxHandle, c_int(i), sval, byref(ival), byref(jval))
       syid = sval.value
       sydim = ival.value
       sytype = jval.value
       assert rc == 1
       if sytype == 4: # GMS_DT_ALIAS
          continue
       rc = gdx.%CGDX%datawriterawstart(wgdxHandle, c_char_p(syid), c_char_p(b''), c_int(sydim), c_int(sytype), c_int(0))
       assert rc == 1
       rc = gdx.%GDX%datareadrawstart(gdxHandle, c_int(i), byref(ival))
       nrecs = ival.value
       assert rc == 1
       for j in range(nrecs):
           rc = gdx.%GDX%datareadraw(gdxHandle, rawuels_p, vals_p, ival_p)
           assert rc == 1
           rc = gdx.%GDX%datawriteraw(wgdxHandle, rawuels, vals)
           assert rc == 1
       rc = gdx.%GDX%datawritedone(wgdxHandle)
       rc = gdx.%GDX%datareaddone(gdxHandle)
   rc = gdx.%GDX%close(wgdxHandle)
   t = printT(flog, t, f, 'ctypes', 'reading and writing symbols raw')
   assert rc == 0

   rc = gdx.%CGDX%openwrite(wgdxHandle, c_char_p(b'out.gdx'), c_char_p(b'ctypes experiment'), byref(ival))
   t = printT(flog, t, f, 'ctypes', 'cgdxopenwrite')
   assert rc == 1

   for i in range(1, symCount + 1):
       rc = gdx.%CGDX%symbolinfo(gdxHandle, c_int(i), sval, byref(ival), byref(jval))
       syid = sval.value
       sydim = ival.value
       sytype = jval.value
       assert rc == 1
       if sytype == 4: # GMS_DT_ALIAS
          continue
       rc = gdx.%CGDX%datawritestrstart(wgdxHandle, c_char_p(syid), c_char_p(b''), c_int(sydim), c_int(sytype), c_int(0))
       assert rc == 1
       rc = gdx.%GDX%datareadstrstart(gdxHandle, c_int(i), byref(ival))
       nrecs = ival.value
       assert rc == 1
       for j in range(nrecs):
           rc = gdx.%CGDX%datareadstr(gdxHandle, uelsptr, vals_p, ival_p)
           assert rc == 1
           rc = gdx.%CGDX%datawritestr(wgdxHandle, uelsptr, vals)
           assert rc == 1
       rc = gdx.%GDX%datawritedone(wgdxHandle)
       rc = gdx.%GDX%datareaddone(gdxHandle)
   rc = gdx.%GDX%close(wgdxHandle)
   t = printT(flog, t, f, 'ctypes', 'reading and writing symbols str')
   assert rc == 0

   rc = gdx.%GDX%close(gdxHandle)
   assert rc == 0

gdx.xfree(byref(gdxHandle))
gdx.xfree(byref(wgdxHandle))

$offEmbeddedCode
$endIf

* C API Test
$ifThen %RUNCAPI% == 1
$  call cp "%gams.sysdir%apifiles/C/api/gdxcc.c" .
$  call cp "%gams.sysdir%apifiles/C/api/gdxcc.h" .
$  call cp "%gams.sysdir%apifiles/C/api/gclgms.h" .
$  ifThen.BC %system.BuildCode%==LEG
$     call which gcc > /dev/null
$     ifE.gcc errorLevel==0
$        call gcc -o libpfcgdx64.so -O -nostartfiles -shared -Wl,-Bsymbolic -pthread pfgdx.c gdxcc.c -I. -fPIC -fvisibility=hidden -DGC_NO_MUTEX
$     if not exist libpfcgdx64.so $log *** Could not build required C libary
$     if not exist libpfcgdx64.so $set RUNCAPI 0
$  elseIf.BC %system.BuildCode%==DAC
$     call which clang > /dev/null
$     ifE.clang errorLevel==0
$        call clang -o libpfcgdx64.dylib -O -dynamiclib -shared gdxcc.c pfgdx.c -ldl -fPIC -fvisibility=hidden -DGC_NO_MUTEX
$     if not exist libpfcgdx64.dylib $log *** Could not build required C libary
$     if not exist libpfcgdx64.dylib $set RUNCAPI 0
$  elseIf.BC %system.BuildCode%==DEG
$     call which gcc > /dev/null
$     ifE.gcc errorLevel==0
$        call gcc -o libpfcgdx64.dylib -O -dynamiclib -shared gdxcc.c pfgdx.c -ldl -fPIC -fvisibility=hidden -DGC_NO_MUTEX
$     if not exist libpfcgdx64.dylib $log *** Could not build required C libary
$     if not exist libpfcgdx64.dylib $set RUNCAPI 0
$  elseIf.BC %system.BuildCode%==WEI
$     call where -q icl > nul
$     ifE.icl errorLevel==0
$        call icl.exe -Fepfcgdx64.dll  -DGC_NO_MUTEX -nologo -MD -I. -O3 gdxcc.c pfgdx.c -LD -link -nodefaultlib:libc.lib
$     if not exist pfcgdx64.dll $log *** Could not build required C libary
$     if not exist pfcgdx64.dll $set RUNCAPI 0
$  else.BC
$     log *** Could not build required C libary (unknow buildCode %system.BuildCode%)
$     set RUNCAPI 0
$  endIf.BC
$endif

$ifThen %RUNCAPI% == 1
$onEmbeddedCode Python:
from ctypes import *

system_directory = r'%gams.sysdir% '.rstrip()
if sys.platform == "linux" or sys.platform == "linux2":
    so_name = "libpfcgdx64.so"
elif sys.platform == "darwin":
    so_name = "libpfcgdx64.dylib"
elif sys.platform == "win32":
    so_name = "pfcgdx64.dll"
else:
    raise Exception(f'unknown OS {sys.platform}')
cgdx = cdll.LoadLibrary(os.path.join('./', so_name))
rc = cgdx.pfinit(c_char_p(system_directory.encode()))
assert rc == 0

for f in gams.get('fgdx'):
   t[1] = 0
   t[2] = time.time()
   rc = cgdx.pfopenread(c_char_p(f.encode()))
   t = printT(flog, t, f, 'capi', 'cgdxopenread')
   assert rc == 0

   rc = cgdx.pfreadraw(c_int(0))
   t = printT(flog, t, f, 'capi', 'reading symbols raw')
   assert rc == 0

   rc = cgdx.pfreadstr(c_int(0), c_int(0))
   t = printT(flog, t, f, 'capi', 'reading symbols str')
   assert rc == 0

   rc = cgdx.pfregister_uels(c_int(0),c_int(0))
   t = printT(flog, t, f, 'capi', 'register uels reading reverse=false')
   assert rc == 0

   rc = cgdx.pfreadmap()
   t = printT(flog, t, f, 'capi', 'reading symbols map reverse=false')
   assert rc == 0

   rc = cgdx.pfclose()
   assert rc == 0
   rc = cgdx.pfopenread(c_char_p(f.encode()))
   t = printT(flog, t, f, 'capi', 'cgdxopenread')
   assert rc == 0

   rc = cgdx.pfregister_uels(c_int(1),c_int(0))
   t = printT(flog, t, f, 'capi', 'register uels reading reverse=true')
   assert rc == 0

   rc = cgdx.pfreadmap()
   t = printT(flog, t, f, 'capi', 'reading symbols map reverse=true')
   assert rc == 0

   rc = cgdx.pfreadraw(c_int(1))
   t = printT(flog, t, f, 'capi', 'reading and writing symbols raw')
   assert rc == 0

   rc = cgdx.pfwritemap(c_int(0))
   t = printT(flog, t, f, 'capi', 'reading raw and writing map reverse=false')
   assert rc == 0

   rc = cgdx.pfwritemap(c_int(1))
   t = printT(flog, t, f, 'capi', 'reading raw and writing map reverse=true')
   assert rc == 0

   rc = cgdx.pfreadstr(c_int(0), c_int(1))
   t = printT(flog, t, f, 'capi', 'reading and writing symbols str reverse=false')
   assert rc == 0

   rc = cgdx.pfreadstr(c_int(1), c_int(1))
   t = printT(flog, t, f, 'capi', 'reading and writing symbols str reverse=true')
   assert rc == 0

   rc = cgdx.pfclose()
   assert rc == 0

rc = cgdx.pffini()
assert rc == 0

$offEmbeddedCode
$endIf

$onEmbeddedCode Python:
if '%LOGFILE%' != 'stdout' and '%LOGFILE%' != 'GAMSLOG':
   flog.close()
$offEmbeddedCode