Project structure for a reactor model built in Python

I use Python for various computational reactor modeling activities. While there are many components to the models I develop, the basic structure of the project folder tends to be similar to the following:

pyreactor/
├── gas_properties.py
├── geometry.py
├── main.py
├── parameters.py
├── results
│   ├── fig1.pdf
│   └── output.txt
└── velocity.py

The parameters.py file contains dictionaries with various model parameters for different components of the model. Each parameter has an associated value and description.

# parameters.py

reactor = {
'd_inner': (0.2, 'Inner diameter [m]'),
'height': (1.5, 'Height of reactor [m]')
}

gas = {
'mu': ([300, 350, 400, 450, 500], 'Gas viscosities [uP]'),
'p': (115_000, 'Gas pressure [Pa]'),
'species': 'CO2',
'temp': (500, 'Temperature of gas [Celsius]'),
}

solid = {
'cd': (0.25, 'Drag coefficient [-]'),
'dp': (0.03, 'Particle diameter [m]'),
'phi': (0.86, 'Particle sphericity [-]'),
'rho': (2500, 'Density of sand particle [kg/m^3]')
}

The Python files labeled gas_properties.py, geometry.py, and velocity.py represent different components of the reactor model. The gas_properties.py file calculates various properties of the gas using parameters defined in parameters.py. If the script is run as __main__ then it prints some results to the console and displays a plot figure:

# gas_properties.py

import matplotlib.pyplot as plt
import parameters as pm

def rhog(mw, p, tk):
mw = mw / 1000
r = 8.3145
rho = (p * mw) / (r * tk)
return rho

# Parameters
# ----------------------------------------------------------------------------

mu = pm.gas['mu']
p = pm.gas['p']
sp = pm.gas['species']
tk = pm.gas['temp']

# Calculate density and plot viscosities
# ----------------------------------------------------------------------------

if sp is 'CO2':
mw = 44
elif sp is 'CO':
mw = 28

rho = rhog(mw, p, tk)

fig1, ax = plt.subplots()
ax.plot(mu)
ax.set_xlabel('Items [-]')
ax.set_ylabel('Gas viscosity [uP]')

if __name__ == "__main__":
print('--- Gas properties ---')
print(f'mw = {mw} g/mol')
print(f'rho = {rho:.2f} kg/m^3')
plt.show()

The geometry.py file calculates dimensions of the reactor. It prints results to the console if run as __main__:

# geometry.py

import numpy as np
import parameters as pm

# Parameters
# ----------------------------------------------------------------------------

d = pm.reactor['d_inner']
h = pm.reactor['height']

# Calculate cross section area and volume
# ----------------------------------------------------------------------------

a_cross = (np.pi / 4) * (d**2)
vol = a_cross * h

if __name__ == "__main__":
print('--- Geometry calculations ---')
print(f'a_cross = {a_cross:.4f} m^2')
print(f'volume = {vol:.4f} m^3')

The velocity.py file estimates velocity of the particles in the system. It imports gas_properties for the gas density that is used in the velocity function ut(). If the script is run as __main__ then results are printed to the console:

# velocity.py

import parameters as pm
from gas_properties import rho as rhog

def ut(cd, dp, rhog, rhos):
g = 9.81
tm1 = 4 * dp * (rhos - rhog) * g
tm2 = 3 * rhog * cd
ut = (tm1 / tm2)**(1 / 2)
return ut

# Parameters
# ----------------------------------------------------------------------------

cd = pm.solid['cd']
d = pm.solid['dp']
rhos = pm.solid['rho']

# Calculate terminal velocity
# ----------------------------------------------------------------------------

ut = ut(cd, d, rhog, rhos)

if __name__ == "__main__":
print('--- Velocity of particle ---')
print(f'ut = {ut:.2f} m/s')

Finally, the main.py file imports all the components gas_properties.py, geometry.py, and velocity.py of the reactor model. This file prints the parameters and results to a text file named output.txt in the results/ folder. The main file also saves plot figures to the results/ folder:

# main.py

import datetime
import parameters as pm
import gas_properties as gp
import geometry as gm
import velocity as ve

# Parameters and results saved to text file in results/ folder
# ----------------------------------------------------------------------------

with open('results/output.txt', 'w') as txt_file:
date = datetime.datetime.now().strftime('%m/%d/%Y')
print(f'Generated on {date} by G.W.', file=txt_file)

print('\n--- Reactor Parameters ---\n', file=txt_file)
for key, value in pm.reactor.items():
print(f'{key:8} {value:10} \t {value}', file=txt_file)

print('\n--- Gas Parameters ---\n', file=txt_file)
for key, value in pm.gas.items():
if type(value) is list:
print(f'{key:8} {value} \t {value}', file=txt_file)
elif key is 'species':
print(f'{key:27} {value}', file=txt_file)
else:
print(f'{key:20} {value:10} \t {value}', file=txt_file)

print('\n--- Solid Parameters ---\n', file=txt_file)
for key, value in pm.solid.items():
print(f'{key:8} {value:10} \t {value}', file=txt_file)

print('\n--- Gas Properties Results ---\n', file=txt_file)
print(f"{'rho':8} {gp.rho:10.2f} \t Density of gas [kg/m^3]", file=txt_file)

print('\n--- Geometry Results ---\n', file=txt_file)
print(f"{'a_cross':10} {gm.a_cross:.4f} \t Cross section area of reactor [m^2]", file=txt_file)
print(f"{'volume':10} {gm.vol:.4f} \t Volume of reactor [m^3]", file=txt_file)

print('\n--- Velocity Results ---\n', file=txt_file)
print(f"{'ut':10} {ve.ut:.2f} \t Terminal velocity [m/s]", file=txt_file)

# Plots saved to file in results/ folder
# ----------------------------------------------------------------------------

gp.fig1.savefig('results/fig1.pdf', bbox_inches='tight')

While this approach works fine, I feel like there may be a better way of organizing the code and folder structure. Some specific questions that come to mind are:

1. Is using a parameters file that contains dictionaries a good practice? I find that having all model parameters in one file is more maintainable and makes it easy to find what parameters are used for the model.
2. Each component of the reactor model is treated as an individual file (or module) which is imported into the main script. Is this an efficient way to handle model components or is there another feature of the Python programming language that I should consider?
3. All results and figures are saved to the results folder. Is this a reasonable approach or should save results elsewhere on the computer?

Please let me know if there are ways to improve my code and project structure. Regarding the parameters file, I'm aware of YAML, JSON, and INI files but for this example I would like to use standard Python 3 features such as a dictionary, list, tuple, set, etc.

Is using a parameters file that contains dictionaries a good practice?

No. It doesn't look like you're actually using the dictionaries for anything other than static lookup (and an iteration); as such, those parameters should simply be in a tuple, copied to locals.

Also, I suggest that you make your parameters uniform (insofar as name, value and unit); something like

from collections import namedtuple

Param = namedtuple('Param', ('name', 'value', 'unit', 'desc'))

params = (
Param('d_inner', 0.2, 'm', 'Inner diameter'),
Param('height', 1.5, 'm', 'Height of reactor'),
# ...
)
locals().update({p.name: p for p in params})

# ...

print('--- Reactor Parameters ---')
print('{:8} {:6} {:4} {:20}'.format('Name', 'Value', 'Unit', 'Description'))
for p in params:
print('{:8} {:<6.2f} {:4} {:20}'.format(p.name, p.value, p.unit, p.desc))
• What is locals? Is it like a collection of global variables? Dec 4 '18 at 20:22
• It's a dictionary of all of the variables in the current scope. Dec 4 '18 at 20:45
• I’d even go further and recommend a config module. That’s usually where you want to store parameters. Dec 4 '18 at 21:26
• Other than the parameters, do you have any comments on using a main script to run everything and where to put results? Dec 4 '18 at 21:59
• Not really, that's all fairly sane Dec 4 '18 at 22:14