# Python Bindings for ContamX 

`contamx-lib.lib` provides an API wrapper around the simulation engine of **CONTAM**, referred to as *ContamX*. `contamx-lib` enables control of CONTAM simulations, or co-simulation, via a C-language API. This Python package, `contamxpy`, provides another layer of access, i.e., Python bindings, to `contamx-lib`.  

[Driver Modules](#DriverModules) are provided that demonstrate the use of `contamxpy.py`. These drivers import `contamxpy`, obtain a `state` associated with a specific CONTAM PRJ file, get properties of the simulation from the `state`, and runs the simulation.

**NOTE** 

``contamx-lib`` allows for co-simulation to be performed according to the following:

* steady airflow with no contaminants
* steady airflow with steady or transient contaminants
* transient airflow with transient contaminants
* Only the Default Solver (Implicit Euler) is allowed for contaminant calculations

Co-simulation is not allowed for the following:

* Airflow simulations: Duct Balance, Building Airflow Test, or Building Pressurization Test
* Contaminant simulations: Cyclic, Short Time Step, or CVODE
* CFD analysis

## 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
```

## <a name="DriverModules"></a>Driver Modules

Demonstration cases are provided with the downloadable source distribution. Example drivers and test cases, i.e., CONTAM PRJ and associated input files (WTH, CTM), will be in the `demo_files` folder. This example command line runs the `test_OneZoneWthCtm.py` module with `test_GetPrjInfo.prj` using the `--verbose=2` command line option and redirects it to a text file named `out.txt`. Review `out.txt` for detailed information obtained from the PRJ file via the `contamxpy` module.  

```
test_OneZoneWthCtm.py test_GetPrjInfo.prj --verbose=2 > out.txt
```
Some driver modules (listed below) are case-specific and provide boundary conditions (WTH and CTM-like) for their respective PRJ files. See detailed information on each driver program in the documentation.

* test_OneZoneWthCtm.py

    - test_OneZoneWthCtm-UseApi.prj
    - test_OneZoneWthCtmStack-UseApi.prj
    - valThreeZonesWthCtm-UseApi.prj
    - testGetPrjInfo.prj

* test_cxcffi.py

    - test_OneZoneWthCtm.prj
    - test_OneZoneWthCtmStack.prj
    - valThreeZonesWthCtm.prj

* test_OneFloorWpcAddMf.py

    - test_OneFloorWpcAddMf-UseApi.prj

* test_OneZoneSS.py

    - test_OneZoneSS-UseApi.prj
    - test_OneZoneSsStack-UseApi.prj

* test_OneZoneSS-Loop.py

    - test_OneZoneSsStack-UseApi.prj

 `contamxpy` is thread safe, and the `test_Multirun.py` driver demonstrates this capability by running multiple PRJ files provided in a list file using the Python `threading` module. **NOTE**: At the time of this release, `contamxpy` cannot be used with Python's multiprocessing capabilities due to pickling limitation of CFFI-based modules.
 
 * test_MultiRun.py (Use --help option for details)

    - prjFile.lst
    - reg_solverContTrace-mz-MH-trans-3day.prj, test_GetPrjInfo.prj, test_OneFloorWpcAddMfYear.prj, test_OneZoneWthCtmStackYear.prj, test_OneZoneWthCtmYear.prj, valThreeZonesWthCtmYear.prj

# Documentation

The *docs* folder in the source distribution file (e.g., *contamxpy-#.#.#.zip*) contains a PDF with detailed documentation of the API and the accompanying demonstration cases.

# NIST 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 statically linked build of ContamX with an API, i.e., `contamx-lib`. A dynamic Python module (.pyd) is built that incorporates the static build.  

**NOTE:** The Windows version of the static build of `contamx-lib.lib` must include the following dependencies: `WSock32.lib`, `WS2_32.lib`, and `user32.lib`. On linux, the static build of `contamx-lib` must be named `libcontamx-lib.a` for wrapping into `contamxpy`.

## 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\
|   └── contam-x-cosim.h
|       string-len-max.h
|       types.h
|
└── demo_files\
    └── cxResults.py
        cxRunSim.py
        test_cxcffi.py
          test_OneZoneWthCtm.prj/.wth/.ctm
          test_OneZoneWthCtmStack.prj/.wth/.ctm
          valThreeZonesWth.prj/.wth/.ctm
          test_OneFloorWpcAddMf.prj
          test_OneFloor.wpc
        test_OneZoneWthCtm.py
          test_OneZoneWthCtm-UseApi.prj
          test_OneZoneWthCtmStack-UseApi.prj
          test_GetPrjInfo.prj
        test_OneFloorWpcAddMf.py
          test_OneFloorWpcAddMf-UseApi.prj
        test_MultiRun.py
          prjFile.lst
          reg_solverContTrace-mz-MH-trans-3day.prj
          MD-Balt-EPW.wth
          test_OneFloorWpcAddMfYear.prj 
          test_OneFloorYear.wpc
          test_OneZoneWthCtmStackYear.prj/.wth/.ctm
          test_OneZoneWthCtmYear.prj/.wth/.ctm
          valThreeZonesWthCtmYear.prj/.wth/.ctm
        test_OneZoneSS.py
          test_OneZoneSS-UseApi.prj
          test_OneZoneSS.prj
          test_OneZoneSS-Transient.prj/.wth/.ctm
        test_OneZoneSS-Loop.py
          test_OneZoneSsStack-UseApi.prj
```
### 2. Create virtual environment
   `python -m venv .venv`  
   
### 3. Activate virtual environment
   + Windows => `.venv\Scripts\activate.bat`
   
### 4. Install required packages:  

   `$ python -m pip install cffi, wheel, numpy`

* *cffi* and *wheel* are required for development
* *cffi* and *numpy* are required to run example driver modules  

### 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 dynamic python module, e.g., 
`_contamxpy-cp310-win_amd64.pyd` that provides a wrapper to `contamx-lib.lib`.

### 6. Install the development version locally
```
$(.venv) pip install .
```
Alternatively, `contamxpy` can be installed from the wheel file after the next step is completed by replacing "." in the above command by the path to the filename of the wheel file that is generated.

### 7. Generate Files for Distribution
Built distribution, i.e., wheel file:
```
$(.venv) python -m setup bdist_wheel
```
Source distribution, i.e., compressed archive (.zip, .gz):
```
$(.venv) python -m setup sdist --format=zip
AND/OR
$(.venv) python -m setup sdist
```

## Development Files  

### cxcffi_build.py

This module is used to produce the dynamic python module using CFFI to wrap the static library, `contamx-lib`.  

```python
from __future__ import annotations

# Using the "out-of-line", "API mode"
from cffi import FFI


CDEF = '''\
    // NOT SHOWN: typedef and function prototypes for contamx-lib.lib
'''

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 listed above. 

NOTE: Code not shown here.

### setup.py  

```python
from setuptools import setup
setup(
    cffi_modules=['contamxpy_build.py:ffibuilder']
    )
```

### setup.cfg

```ini
[metadata]
name = contamxpy
version = 0.0.8
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

[bdist_wheel]
py-limited-api=cp37
```

### MANIFEST.in
The manifest file is used to add files to the source builds.  

```
include contamx-lib.*
include libcontamx-lib.*
include contamxpy_build.py
include include/*.h
include demo_files/*.py
include demo_files/*.prj
include demo_files/*.wth
include demo_files/*.ctm
include docs/Makefile
include docs/make.bat
include docs/source/*.rst
include docs/source/conf.py
include docs/build/latex/contamxpy.pdf
```

# REFERENCES
1. https://www.youtube.com/watch?v=X5irxO5VCHw
2. https://github.com/asottile/ukkonen
3. https://cffi.readthedocs.io/en/latest/index.html
4. https://docs.python.org/3.10/distutils/index.html
5. https://setuptools.pypa.io/en/latest/setuptools.html
6. https://packaging.python.org/en/latest/tutorials/packaging-projects/

# TODO  
- Provide static build of `libcontamx-lib.a` for Linux.
