Python wrapper for the CONTAM Simulation Engine, ContamX
Project description
Python Bindings for ContamX
NOTE: This package is not yet fully functional.
This is the initial implementation of a Python wrapper for contamx-lib.lib
which is a statically linked library with an API wrapper around ContamX. contamx-lib
enables control of CONTAM simulations via C-language API. This Python package, contamxpy
, provides another layer of access, i.e., Python bindings, to contamx-lib
.
The demonstration driver, test_cxffi.py
that is included herein, imports contamxpy
, obtains a state
, gets properties of the simulation necessary to run from beginning to end, then steps through all the time steps.
Usage
Typically, one would work within a Python virtual environment which can be created and activated using the following commands.
$ python -m venv .venv
$ .venv\Scripts\activate (on Windows)
Install contamxpy
from PyPI.
$(.venv) python -m pip install contamxpy
Get help for the test module from the command line.
test_cxcffi.py -h
Usage: test_cxcffi.py [options] arg1
arg1=PRJ filename
Options:
-h, --help show this help message and exit
-v VERBOSE, --verbose=VERBOSE
define verbose output level: 0=Min, 1=Medium,
2=Max
Run example case.
test_cxcffi.py testOneZoneWthCtm.prj
Example Test Module: test_cxcffi.py
from contamxpy import cxLib
import os, sys
from optparse import OptionParser
#===================================================================================== main() =====
def main():
#----- Manage option parser
parser = OptionParser(usage="%prog [options] arg1\n\targ1=PRJ filename\n")
parser.set_defaults(verbose=0)
parser.add_option("-v", "--verbose", action="store", dest="verbose", type="int", default=0,
help="define verbose output level: 0=Min, 1=Medium, 2=Maximum.")
(options, args) = parser.parse_args()
#----- Process command line options -v
verbose = options.verbose
if len(args) != 1:
parser.error("Need one argument:\n arg1 = PRJ file.")
return
else:
prj_name = args[0]
if ( not os.path.exists(prj_name) ):
print("ERROR: PRJ file not found.")
return
msg_cmd = "Running test_cxcffi.py: arg1 = " + args[0] + " " + str(options)
print(msg_cmd, "\n")
if verbose > 1:
print(f"cxLib attributes =>\n{chr(10).join(map(str, dir(cxLib)))}\n")
#----- Initialize contamx-lib object w/ wpMode and cbOption.
# wpMode = 0 => use wind pressure profiles, WTH and CTM files or associated API calls.
# cbOption = True => set callback function to get PRJ INFO from the ContamXState.
myPrj = cxLib(0, True)
myPrj.setVerbosity(verbose)
if verbose > 1:
print(f"BEFORE setupSimulation()\n\tnCtms={myPrj.nContaminants}\n\tnZones={myPrj.nZones}\n\tnPaths={myPrj.nPaths}\n" )
#----- Query State for Version info
verStr = myPrj.getVersion()
if verbose >= 0:
print(f"getVersion() returned {verStr}.")
#----- Setup Simulation for PRJ
myPrj.setupSimulation(prj_name, 1)
if verbose > 1:
print(f"AFTER setupSimulation()\n\tnCtms={myPrj.nContaminants}\n\tnZones={myPrj.nZones}\n\tnPaths={myPrj.nPaths}\n" )
dayStart = myPrj.getSimStartDate()
dayEnd = myPrj.getSimEndDate()
secStart = myPrj.getSimStartTime()
secEnd = myPrj.getSimEndTime()
tStep = myPrj.getSimTimeStep()
simBegin = (dayStart - 1) * 86400 + secStart
simEnd = (dayEnd - 1) * 86400 + secEnd
#----- Calculate the simulation duration in seconds and total time steps
if (simBegin < simEnd):
simDuration = simEnd - simBegin
else:
simDuration = 365 * 86400 - simEnd + simBegin
numTimeSteps = int(simDuration / tStep)
#----- Get the current date/time after initial steady state simulation
currentDate = myPrj.getCurrentDayOfYear()
currentTime = myPrj.getCurrentTimeInSec()
if verbose > 0:
print(f"Sim days = {dayStart}:{dayEnd}")
print(f"Sim times = {secStart}:{secEnd}")
print(f"Sim time step = {tStep}")
print(f"Number of steps = {numTimeSteps}")
#----- Run Simulation
for i in range(numTimeSteps):
currentDate = myPrj.getCurrentDayOfYear()
currentTime = myPrj.getCurrentTimeInSec()
if verbose > 1:
print(f"{i}\t{currentDate},{currentTime}")
myPrj.doSimStep(1)
myPrj.endSimulation()
# --- End main() ---#
if __name__ == "__main__":
main()
Developer Notes
These bindings were developed using the C Foreign Function Interface (CFFI). CFFI utilizes C header files that define the contamx-lib
API. contamx-lib.lib
is a build of ContamX that is statically linked build of contamx-lib
. A dynamic Python module (.pyd) is built that incorporates the static build.
NIST Developer NOTE: The static build of contamx-lib.lib
must include the following dependencies: WSock32.lib
, WS2_32.lib
, and user32.lib
.
Steps to Develop Python Bindings
1. Create directory for contamxpy
- Either clone the repo (NIST developers only) OR
- Download the source distribution from https://pypi.org/project/contamxpy/
Source files:
contamxpy\
|
| setup.py
| setup.cfg
| MANIFEST.in
| LICENSE.txt
| README.md
| contamxpy_build.py
| contamxpy.py
| contamx-lib.lib
|
├── include\
| └── common-api.h
| commonState.h
| contam-x-cosim.h
| contam-x-state.h
| element-api.h
| flags.h
| library-api.h
| project-api.h
| string-len-max.h
| types.h
|
└── demo_files\
└── test_cxcffi.py
testOneZoneWthCtm.prj/.wth/.ctm
valThreeZonesWth.prj/.wth/.ctm
2. Create virtual environment
python -m venv .venv
3. Activate virtual environment
- Windows =>
.venv\Scripts\activate.bat
4. Install cffi and wheel packages
$ python -m pip install cffi, wheel
5. Generate contamxpy
Run the build module.
contamxpy_build.py
This should generate _contamxpy.c, etc.
contamxpy\
|
├── Release\
| └── *.exp/.lib/.obj
|
└── _contamxpy.c
_contamxpy.cp310-win_amd64.pyd
Most importantly it will generate a .pyd file, e.g.,
_contamxpy-0.0.3-abi3-cp310-win_amd64.pyd
. This is a dynamic Python module to be imported into a driver program. It provides a wrapper to contamx-lib.lib
.
6. Install the development version locally
$(.venv) pip install .
7. Generate Files for Distribution
Built distribution, i.e., wheel file:
$(.venv) python -m setup bdist_wheel
Source distribution, i.e., compressed archive (.gz, .zip):
$(.venv) python -m setup sdist
$(.venv) python -m setup sdist --format=zip
Development Files
cxcffi_build.py
The file shown below is a partial implementation of the
API associated with contamx-lib
.
from __future__ import annotations
# Using the "out-of-line", "API mode"
from cffi import FFI
CDEF = '''\
// see types.h
typedef int32_t IX;
typedef float R4;
typedef double R8;
typedef uint16_t U2;
typedef struct zone_cosim_dsc
{
int32_t nr;
uint16_t flags;
float Vol;
char level_name[16]; // #define NAMELEN 16
int32_t level_nr;
char name[32]; // #define NMLN 32
} ZONE_COSIM_DSC;
typedef struct path_cosim_dsc
{
IX nr;
U2 flags;
IX from_zone;
IX to_zone;
IX ahs_nr;
R4 X;
R4 Y;
R4 Z;
IX envIndex;
} PATH_COSIM_DSC;
// Callback function - extern => to be python function defined in contamxpy.py.
extern "Python" void prjDataReadyFcnP(void *, void *);
// {prjDataReadyFcnP} will set to function defined in {cxLib} class of {contamxpy}.
void cxiRegisterCallback_PrjDataReady( void* contamXState, void* pUserData, void ( *prjDataReadyFcnP )( void*, void* ) );
void* cxiGetContamState();
void cxiSetWindPressureMode(void* contamXState, IX useWP);
void cxiSetupSimulation(void* contamXState, char* projectPath, IX useCosim);
IX cxiGetVersion(void* contamXState, char* strVersion);
IX cxiGetSimulationStartDate(void* contamXState);
IX cxiGetSimulationStartTime(void* contamXState);
IX cxiGetSimulationEndDate(void* contamXState);
IX cxiGetSimulationEndTime(void* contamXState);
IX cxiGetSimulationTimeStep(void* contamXState);
IX cxiGetCurrentDate(void* contamXState);
IX cxiGetCurrentTime(void* contamXState);
void cxiDoCoSimStep(void* contamXState, IX stepForward);
void cxiEndSimulation(void* contamXState);
// These functions will be utilized within the callback function, prjDataReadyFcnP(),
// to populate class variables for access by the calling/driver
// program which imports {contamxpy}
IX cxiGetNumCtms(void* contamXState);
IX cxiGetCtmName(void* contamXState, IX ctmNumber, char* strName);
IX cxiGetNumZones(void* contamXState);
IX cxiGetZoneInfo(void* contamXState, IX zoneNumber, ZONE_COSIM_DSC* pZoneInfo);
IX cxiGetNumPaths(void* contamXState);
IX cxiGetPathInfo(void* contamXState, IX pathNumber, PATH_COSIM_DSC* pPath);
// Results
IX cxiGetZoneMF(void* contamXState, IX zoneNumber, IX ctmNumber, R8* pMassFraction);
'''
SRC = '''\
#include "include//contam-x-cosim.h"
'''
ffibuilder = FFI()
ffibuilder.cdef(CDEF)
ffibuilder.set_source(
"_contamxpy", SRC,
include_dirs=['.','include'], # C header files for contam-x-lib
libraries=['contamx-lib'], # Library to link with (.lib, .dll)
)
if __name__ == "__main__":
ffibuilder.compile(verbose=True)
contamxpy
contamxpy
implements the cxLib
class that provides the wrapper to contamx-lib
. This module is imported for use by driver programs as demonstrated in test_cxcffi.py above.
from __future__ import annotations
import _contamxpy
_lib = _contamxpy.lib
_ffi = _contamxpy.ffi
#print(f"_contamxpy => \n\t{dir(_contamxpy)}")
'''
cxiGetContamState() is defined in contam-x-cosim.c
'''
def getState():
return _lib.cxiGetContamState()
def setWindPressureMode(state, mode):
_lib.cxiSetWindPressureMode(state, mode)
def getVersion(state):
bufStr = _ffi.new("char[]", 64)
_lib.cxiGetVersion(state, bufStr)
return _ffi.string(bufStr).decode('utf-8')
def setupSimulation(state, prjPath, useCosim):
_lib.cxiSetupSimulation(state, prjPath.encode('ascii'), useCosim)
def getSimTimeStep(state):
timeStep = _lib.cxiGetSimulationTimeStep(state)
return timeStep
def getSimStartDate(state):
dayOfYear = _lib.cxiGetSimulationStartDate(state)
return dayOfYear
def getSimEndDate(state):
dayOfYear = _lib.cxiGetSimulationEndDate(state)
return dayOfYear
def getSimStartTime(state):
timeOfDaySeconds = _lib.cxiGetSimulationStartTime(state)
return timeOfDaySeconds
def getSimEndTime(state):
timeOfDaySeconds = _lib.cxiGetSimulationEndTime(state)
return timeOfDaySeconds
def getCurrentDayOfYear(state):
return _lib.cxiGetCurrentDate(state)
def getCurrentTimeInSec(state):
return _lib.cxiGetCurrentTime(state)
def doSimStep(state, stepForward):
_lib.cxiDoCoSimStep(state, stepForward)
def endSimulation(state):
_lib.cxiEndSimulation(state)
setup.py
from __future__ import annotations
import platform
import sys
from setuptools import setup
if platform.python_implementation() == 'CPython':
try:
import wheel.bdist_wheel
except ImportError:
cmdclass = {}
else:
class bdist_wheel(wheel.bdist_wheel.bdist_wheel):
def finalize_options(self) -> None:
self.py_limited_api = f'cp3{sys.version_info[1]}'
super().finalize_options()
cmdclass = {'bdist_wheel': bdist_wheel}
else:
cmdclass = {}
setup(
cffi_modules=['contamxpy_build.py:ffibuilder'], cmdclass=cmdclass
)
setup.cfg
[metadata]
name = contamxpy
version = 0.0.3
description = Python wrapper for the CONTAM Simulation Engine, ContamX
long_description = file: README.md
long_description_content_type = text/markdown
author = W. Stuart Dols, Brian Polidoro, NIST
author_email = william.dols@nist.gov
license = Public Domain
license_files = LICENSE.txt
classifiers =
License :: Public Domain
License :: OSI Approved :: BSD License
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
Topic :: Scientific/Engineering
project_urls=
Web Page= https://www.nist.gov/el/energy-and-environment-division-73200/nist-multizone-modeling/
Source = https://pypi.org/project/contamxpy/
[options]
py_modules = contamxpy
python_requires = >=3.7
install_requires =
cffi>=1
setup_requires =
cffi>=1
MANIFEST.in
The manifest file is used to add files to the source builds.
include include\*.h
include contamx-lib.*
include demo_files\*.*
REFERENCES
- https://www.youtube.com/watch?v=X5irxO5VCHw
- https://github.com/asottile/ukkonen
- https://cffi.readthedocs.io/en/latest/index.html
- https://docs.python.org/3.10/distutils/index.html
- https://setuptools.pypa.io/en/latest/setuptools.html
- https://packaging.python.org/en/latest/tutorials/packaging-projects/
TODO
- Implement full API.
- Test on Linux
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for contamxpy-0.0.3-cp310-abi3-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | bbe9f82664e549d96f30dc6cf82b7842711292cf4b32918a8a7df7ddc1cfe339 |
|
MD5 | 4f27e113499c74ba5721702ec919f077 |
|
BLAKE2b-256 | 6d844b22a2e802a3325fbb2ff51f2e538388f77abeb72d55cdc65f2e43a5e581 |