From ba8224804b82d779806e01977ae96ab6a392ab04 Mon Sep 17 00:00:00 2001 From: dmontgomeryNREL Date: Wed, 27 Aug 2025 12:23:47 -0600 Subject: [PATCH 1/9] Reorg, rename groupContribution class to fuel --- docs/conf.py | 2 +- docs/{groupcontribution.rst => fuelprops.rst} | 33 ++++--- docs/index.rst | 2 +- docs/sourcecode.rst | 25 +++--- docs/tutorials.rst | 51 +++++++---- paths.py | 15 ++++ .../Export4Converge.py | 53 +++++------ Export4Pele.py => source/Export4Pele.py | 89 +++++++++++++------ FuelLib.py => source/FuelLib.py | 74 ++++++++------- tests/test_accuracy.py | 12 ++- tests/test_baseline.py | 11 ++- tests/test_functions.py | 13 +-- tutorials/basic.py | 10 +-- tutorials/compositionPlots.py | 13 +-- tutorials/hefaBlends.py | 18 ++-- tutorials/mixtureProperties.py | 16 ++-- 16 files changed, 255 insertions(+), 182 deletions(-) rename docs/{groupcontribution.rst => fuelprops.rst} (95%) create mode 100644 paths.py rename Export4Converge.py => source/Export4Converge.py (91%) rename Export4Pele.py => source/Export4Pele.py (82%) rename FuelLib.py => source/FuelLib.py (93%) diff --git a/docs/conf.py b/docs/conf.py index c28be09..960620d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -5,7 +5,7 @@ import os import sys -sys.path.insert(0, os.path.abspath("..")) +sys.path.insert(0, os.path.abspath("../source")) # -- Project information ----------------------------------------------------- diff --git a/docs/groupcontribution.rst b/docs/fuelprops.rst similarity index 95% rename from docs/groupcontribution.rst rename to docs/fuelprops.rst index e587dba..49c4e4b 100644 --- a/docs/groupcontribution.rst +++ b/docs/fuelprops.rst @@ -1,8 +1,7 @@ -Properties and Model Equations +Fuel Property Prediction Model ============================== -The **Fuel Library** for advanced research on evaporation **(FuelLib)** utilizes -the group contribution method (GCM), as developed by Constantinou and +**FuelLib** utilizes the group contribution method (GCM), as developed by Constantinou and Gani\ :footcite:p:`constantinou_new_1994` \ :footcite:p:`constantinou_estimation_1995` in the mid-1990s, to provide a systematic approach for estimating the thermodynamic properties of pure organic compounds. The GCM decomposes molecules into structural groups, @@ -143,7 +142,7 @@ provided :math:`T` in K unless noted otherwise. Kinematic viscosity ^^^^^^^^^^^^^^^^^^^ -.. automethod:: FuelLib.groupContribution.viscosity_kinematic +.. automethod:: FuelLib.fuel.viscosity_kinematic :noindex: The kinematic viscosity of the *i-th* compound of the fuel, @@ -166,7 +165,7 @@ Liquids\ :footcite:p:`viswanath_viscosity_2007`) provided :math:`T` in :math:`^{ Latent heat of vaporization ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automethod:: FuelLib.groupContribution.latent_heat_vaporization +.. automethod:: FuelLib.fuel.latent_heat_vaporization :noindex: The latent heat of vaporization for each compound at standard pressure and @@ -185,7 +184,7 @@ temperature\ :footcite:p:`govindaraju_group_2016`: Liquid molar volume ^^^^^^^^^^^^^^^^^^^ -.. automethod:: FuelLib.groupContribution.molar_liquid_vol +.. automethod:: FuelLib.fuel.molar_liquid_vol :noindex: The liquid molar volume is calculated at a specific temperature :math:`T` using @@ -211,7 +210,7 @@ where Density ^^^^^^^ -.. automethod:: FuelLib.groupContribution.density +.. automethod:: FuelLib.fuel.density :noindex: The density of the *i-th* compound is given by @@ -223,7 +222,7 @@ The density of the *i-th* compound is given by Liquid specific heat capacity ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automethod:: FuelLib.groupContribution.Cl +.. automethod:: FuelLib.fuel.Cl :noindex: The liquid specific heat capacity for each compound at standard pressure temperature is calculated from the specific heat capacity as: @@ -236,7 +235,7 @@ The liquid specific heat capacity for each compound at standard pressure tempera Saturated vapor pressure ^^^^^^^^^^^^^^^^^^^^^^^^ -.. automethod:: FuelLib.groupContribution.psat +.. automethod:: FuelLib.fuel.psat :noindex: The saturated vapor pressure for each compound is calculated as a function of @@ -275,7 +274,7 @@ with :math:`\tau_i = 1 - T_{r,i}`. Surface tension ^^^^^^^^^^^^^^^ -.. automethod:: FuelLib.groupContribution.surface_tension +.. automethod:: FuelLib.fuel.surface_tension :noindex: Surface tension for each compound is approximated using the relation: @@ -298,7 +297,7 @@ or by Curl and Pitzer\ :footcite:p:`poling_properties_2001` \ :footcite:p:`curl_ Thermal conductivity ^^^^^^^^^^^^^^^^^^^^ -.. automethod:: FuelLib.groupContribution.thermal_conductivity +.. automethod:: FuelLib.fuel.thermal_conductivity :noindex: Thermal conductivity for each compound is computed according to the method of @@ -411,7 +410,7 @@ where :math:`Q_i` is the property of the *i-th* compound of the multicomponent m Mixture density ^^^^^^^^^^^^^^^ -.. automethod:: FuelLib.groupContribution.mixture_density +.. automethod:: FuelLib.fuel.mixture_density :noindex: The mixture's density is calculated as: @@ -424,7 +423,7 @@ The mixture's density is calculated as: Mixture kinematic viscosity ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automethod:: FuelLib.groupContribution.mixture_kinematic_viscosity +.. automethod:: FuelLib.fuel.mixture_kinematic_viscosity :noindex: The kinematic viscosity of the mixture is computed using the Kendall-Monroe\ :footcite:p:`kendall_viscosity_1917` @@ -449,7 +448,7 @@ The Arrhenius rule is: Mixture vapor pressure ^^^^^^^^^^^^^^^^^^^^^^ -.. automethod:: FuelLib.groupContribution.mixture_vapor_pressure +.. automethod:: FuelLib.fuel.mixture_vapor_pressure :noindex: The vapor pressure of the mixture is calculated according to Raoult's law: @@ -459,7 +458,7 @@ The vapor pressure of the mixture is calculated according to Raoult's law: p_{v} = \sum_{i = 1}^{N_c} X_i \, p_{\textit{sat},i}. \end{align*} -.. automethod:: FuelLib.groupContribution.mixture_vapor_pressure_antoine_coeffs +.. automethod:: FuelLib.fuel.mixture_vapor_pressure_antoine_coeffs :noindex: Users also have the option to return the coefficients from an Antoine fit based on @@ -479,7 +478,7 @@ for additional information. Mixture surface tension ^^^^^^^^^^^^^^^^^^^^^^^ -.. automethod:: FuelLib.groupContribution.mixture_surface_tension +.. automethod:: FuelLib.fuel.mixture_surface_tension :noindex: The surface tension of the mixture is calculated using the :ref:`conventional-mixing-rules` @@ -492,7 +491,7 @@ Hugill and van Welsenes\ :footcite:p:`hugill_surface_1986`: Mixture thermal conductivity ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automethod:: FuelLib.groupContribution.mixture_thermal_conductivity +.. automethod:: FuelLib.fuel.mixture_thermal_conductivity :noindex: The thermal conductivity of the mixture is calculated using the power law method of diff --git a/docs/index.rst b/docs/index.rst index bca4088..82ef632 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -42,7 +42,7 @@ If you use FuelLib in your research, please cite the following software record: :includehidden: :caption: Contents: - groupcontribution + fuelprops sourcecode tutorials diff --git a/docs/sourcecode.rst b/docs/sourcecode.rst index 657b105..05b6fff 100644 --- a/docs/sourcecode.rst +++ b/docs/sourcecode.rst @@ -9,27 +9,32 @@ FuelLib File Organization ------------------------- - **docs:** directory containing the documentation source files -- **tutorials:** directory containing example scripts that demonstrate how to use FuelLib - - - ``basic.py``: example script that demonstrates basic usage of FuelLib - - ``compositionPlots.py``: example script that generates composition plots for a given fuel - - ``hefaBlends.py``: example script that calculates properties of HEFA:Jet-A blends - - ``mixtureProperties.py``: validation script that calculates properties of single component fuels and mixture properties of multicomponent fuels. -- ``Export4Converge.py``: script that exports mixture properties over a range of user specified temperatures for use in Converge simulations. -- ``Export4Pele.py``: script that exports critical properties and initial mass fraction data for use in Pele simulations. - **fuelData:** - **gcData:** directory containing a collection of GCxGC compositional data by weight percentages - **groupDecompositionData:** directory containing a collection of functional group decompositions - **propertiesData:** directory containing measurement or predicted data for validation (see *fuelData/dataReferences.md*) - **gcmTableData:** directory that contains the pre-tabulated group contributions -- ``FuelLib.py``: class for enabling GCM predictions +- **source:** directory containing the main source code files + + - ``Export4Converge.py``: script that exports mixture properties over a range of user specified temperatures for use in Converge simulations. + - ``Export4Pele.py``: script that exports critical properties and initial mass fraction data for use in Pele simulations. + - ``FuelLib.py``: class for enabling GCM predictions + - **tests:** directory containing CI unit tests for FuelLib. The CI test checks if the cumulative error of property predictions of a new proposed model are less than or equal to the current model. - **baselinePredictions:** directory that contains baseline predictions - ``test_accuracy.py``: unit test used in CI for verifying new model predictions preserve accuracy - ``test_baseline.py``: generates .csv files for the baseline model predictions, which are stored in **baselinePredictions** - - ``test_functions.py``: collection of functions used by ``test_baseline.py`` and ``test_accuracy.py``. + - ``test_functions.py``: collection of functions used by ``test_baseline.py`` and ``test_accuracy.py``. + +- **tutorials:** directory containing example scripts that demonstrate how to use FuelLib + + - ``basic.py``: example script that demonstrates basic usage of FuelLib + - ``compositionPlots.py``: example script that generates composition plots for a given fuel + - ``hefaBlends.py``: example script that calculates properties of HEFA:Jet-A blends + - ``mixtureProperties.py``: validation script that calculates properties of single component fuels and mixture properties of multicomponent fuels. +- ``paths.py``: file that defines paths to various directories and files used in FuelLib Source Code Auto-Documentation ------------------------------ diff --git a/docs/tutorials.rst b/docs/tutorials.rst index fac6e72..85788f5 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -15,9 +15,9 @@ Create and activate a Conda environment, install the required dependencies: :: conda create --name fuellib-env matplotlib pandas scipy conda activate fuellib-env -Change to the FuelLib directory: :: +Change to the FuelLib/tutorials directory: :: - cd FuelLib + cd FuelLib/tutorials Required Input files ^^^^^^^^^^^^^^^^^^^^^ @@ -43,17 +43,17 @@ as ``basic.py``. To begin, we will import the necessary modules and create a ``g import os import sys - import numpy as np # Add the FuelLib directory to the Python path - fuellib_dir = os.path.dirname(os.path.dirname(__file__)) - sys.path.append(fuellib_dir) + FUELLIB_DIR = os.path.dirname(os.path.dirname(__file__)) + sys.path.append(FUELLIB_DIR) + import paths import FuelLib as fl - # Create a groupContribution object for the fuel "heptane-decane" - fuel = fl.groupContribution("heptane-decane") + # Create a fuel object for the fuel "heptane-decane" + fuel = fl.fuel("heptane-decane") -Upon initialization, the ``groupContribution`` object will read the initial weight +Upon initialization, the ``fuel`` object will read the initial weight percentage composition and group decomposition data from the specified files. The object stores vectors of the calculated fundamental properties at standard conditions for each component of the fuel as described in :ref:`eq-GCM-properties`. For example, we can display the fuel name, the components in the fuel, the initial composition, and the critical temperature for each component: @@ -68,10 +68,10 @@ For example, we can display the fuel name, the components in the fuel, the initi .. code-block:: none - Fuel name: heptane-decane - Fuel components: ['NC7H16', 'NC10H22'] - Initial composition: [0.7375 0.2625] - Critical temperature: [549.85598051 623.69051582] K + >> Fuel name: heptane-decane + >> Fuel components: ['NC7H16', 'NC10H22'] + >> Initial composition: [0.7375 0.2625] + >> Critical temperature: [549.85598051 623.69051582] K Next, we can calculate any of the component- or mixture-level properties using the ``groupContribution`` object. For example, we can calculate the saturated vapor pressure @@ -88,8 +88,8 @@ for each component and the mixture at a given temperature: .. code-block:: none - Saturated vapor pressure at 320 K: [13735.84605413 673.28876023] Pa - Mixture saturated vapor pressure at 320 K: 11117.84926875165 Pa + >> Saturated vapor pressure at 320 K: [13735.84605413 673.28876023] Pa + >> Mixture saturated vapor pressure at 320 K: 11117.84926875165 Pa The following links provide more information on the :ref:`eq-GCM-correlations` and the :ref:`eq-mixture-properties` that can be calculated using the ``groupContribution`` object. @@ -133,10 +133,19 @@ Default Options From the ``FuelLib`` directory, run the following command in the terminal, noting that ``--fuel_name`` is the only required input: :: + cd FuelLib/source python Export4Pele.py --fuel_name heptane-decane -This generates the following input file, ``FuelLib/sprayPropsGCM/sprayPropsGCM_heptane-decane.inp``, for use in a PeleLMeX simulation: :: +This generates the following input file, ``FuelLib/exportData/sprayPropsGCM_heptane-decane.inp``, for use in a PeleLMeX simulation: :: + + # ----------------------------------------------------------------------------- + # sprayPropsGCM_heptane-decane.inp + # Generated on + # FuelLib remote URL: https://github.com/NREL/FuelLib.git + # Git commit: + # Units: MKS + # ----------------------------------------------------------------------------- particles.spray_fuel_num = 2 particles.fuel_species = NC7H16 NC10H22 @@ -165,7 +174,7 @@ This generates the following input file, ``FuelLib/sprayPropsGCM/sprayPropsGCM_h particles.NC10H22_cp = 1630.488028169014 3098.1056338028166 -1024.456338028169 # J/kg/K particles.NC10H22_latent = 368035.211268 # J/kg -To include these parameters in your Pele simulation, copy the ``sprayPropsGCM.inp`` +To include these parameters in your Pele simulation, copy the ``sprayPropsGCM_heptane-decane.inp`` file to the specific case directory and include the following line in your Pele input file: :: FILE = sprayPropsGCM.inp @@ -177,6 +186,7 @@ namely the name of the fuel. This is designed for conventional jet fuels such as 67 liquid fuel species corresponding to the GCxGC data, but only a single gas-phase mechanism species, "POSF10325". For example: :: + cd FuelLib/source python Export4Pele.py --fuel_name posf10325 will result in the following: :: @@ -197,10 +207,11 @@ There are four additional options that can be specified when running the export - ``--units``: Specify the units for the properties. The default is "mks" but users can set the units to "cgs" for use in PeleC. - ``--dep_fuel_names``: Specify which gas-phase species the liquid fuel deposits. The default is the same as the fuel name, but users can specify a single gas-phase species or a list of gas-phase species. - ``--max_dep_fuels``: Specify the maximum number of dependent fuels. The default is 30 and is a bit arbitrary. -- ``--export_dir``: Specify the directory to export the file. The default is "FuelLib/sprayPropsGCM". +- ``--export_dir``: Specify the directory to export the file. The default is "FuelLib/exportData". To specify all liquid fuel species deposity to a single gas-phase species, run the following command: :: + cd FuelLib/source python Export4Pele.py --fuel_name heptane-decane --dep_fuel_names SINGLE_GAS This will result in the following: :: @@ -233,6 +244,7 @@ If there are more than 30 components and the user wants each component to deposi to a gas-phase species of the same name, the user can increase ``--max_dep_fuels`` to a value greater than 30, however this would be required a massive mechanism for Pele and is not advised :: + cd FuelLib/source python Export4Pele.py --fuel_name posf10325 --max_dep_fuels 67 @@ -262,10 +274,11 @@ Default Options From the ``FuelLib`` directory, run the following command in the terminal, noting that ``--fuel_name`` is the only required input: :: + cd FuelLib/source python Export4Converge.py --fuel_name posf10325 -This generates the file ``FuelLib/mixturePropsGCM/mixturePropsGCM_posf10325.csv`` with mixture +This generates the file ``FuelLib/exportData/mixturePropsGCM_posf10325.csv`` with mixture property predictions from 0 K to 1000 K for use in a Converge simulation. Additional Options @@ -277,7 +290,7 @@ There are four additional options that can be specified when running the export - ``--temp_min``: Specify the minimum temperature. The default is 0 K. - ``--temp_max``: Specify the maximum temperature. The default is 1000 K. - ``--temp_step``: Specify the temperature step size. The default is :math:`\Delta T = 10` K. -- ``--export_dir``: Specify the directory to export the file. The default is "FuelLib/mixturePropsGCM". +- ``--export_dir``: Specify the directory to export the file. The default is "FuelLib/exportData". .. note:: The mixture property predictions may not be valid from the specified ``temp_min`` to ``temp_max``, diff --git a/paths.py b/paths.py new file mode 100644 index 0000000..a3e68f9 --- /dev/null +++ b/paths.py @@ -0,0 +1,15 @@ +import os +import sys + +FUELLIB_DIR = os.path.dirname(__file__) +GCMTABLE_DIR = os.path.join(FUELLIB_DIR, "gcmTableData") +SOURCE_DIR = os.path.join(FUELLIB_DIR, "source") +FUELDATA_DIR = os.path.join(FUELLIB_DIR, "fuelData") +FUELDATA_GC_DIR = os.path.join(FUELDATA_DIR, "gcData") +FUELDATA_DECOMP_DIR = os.path.join(FUELDATA_DIR, "groupDecompositionData") +FUELDATA_PROPS_DIR = os.path.join(FUELDATA_DIR, "propertiesData") +TESTS_DIR = os.path.join(FUELLIB_DIR, "tests") +TESTS_BASELINE_DIR = os.path.join(TESTS_DIR, "baselinePredictions") +TUTORIALS_DIR = os.path.join(FUELLIB_DIR, "tutorials") + +sys.path.append(SOURCE_DIR) diff --git a/Export4Converge.py b/source/Export4Converge.py similarity index 91% rename from Export4Converge.py rename to source/Export4Converge.py index 7eb9da9..2df463a 100644 --- a/Export4Converge.py +++ b/source/Export4Converge.py @@ -1,9 +1,16 @@ -import pandas as pd -import numpy as np import os +import sys +import numpy as np +import pandas as pd import argparse import FuelLib as fl +# Add the FuelLib directory to the Python path +FUELLIB_DIR = os.path.dirname(os.path.dirname(__file__)) +if FUELLIB_DIR not in sys.path: + sys.path.append(FUELLIB_DIR) +from paths import * + """ Script that exports mixture properties over large temperature range for use in Converge simulations. @@ -24,9 +31,7 @@ """ -def export_converge( - fuel, path="mixturePropsGCM", units="mks", temp_min=0, temp_max=1000, temp_step=10 -): +def export_converge(fuel, path, units, temp_min, temp_max, temp_step): """ Export mixture fuel properties to .csv for Converge simulations. @@ -34,19 +39,19 @@ def export_converge( :type fuel: groupContribution object :param path: Directory to save the input file. - :type path: str, optional + :type path: str :param units: Units for the properties ("mks" for SI, "cgs" for CGS). - :type units: str, optional + :type units: str :param temp_min: Minimum temperature (K) for the property calculations. - :type temp_min: float, optional + :type temp_min: float :param temp_max: Maximum temperature (K)for the property calculations. - :type temp_max: float, optional + :type temp_max: float :param temp_step: Step size for temperature (K). - :type temp_step: float, optional + :type temp_step: float :return: None :rtype: None @@ -247,7 +252,7 @@ def main(): :param --temp_step: Step size for temperature (K) (optional, default: 10). :type --temp_step: float, optional - :param --export_dir: Directory to export the properties. Default is "sprayPropsGCM". + :param --export_dir: Directory to export the properties. Default is "FuelLib/exportData". :type --export_dir: str, optional :raises FileNotFoundError: If required files for the specified fuel are not found. @@ -300,8 +305,8 @@ def main(): # Optional argument for export directory parser.add_argument( "--export_dir", - default="mixturePropsGCM", - help="Directory to export the properties (optional, default: mixturePropsGCM).", + default=os.path.join(FUELLIB_DIR, "exportData"), + help="Directory to export the properties (optional, default: FuelLib/exportData).", ) # Parse arguments @@ -324,33 +329,23 @@ def main(): # Check if necessary files exist in the fuelData directory print("\nChecking for required files...") - decomp_dir = os.path.join( - fl.groupContribution.fuelDataDir, "groupDecompositionData" - ) - gcxgc_dir = os.path.join(fl.groupContribution.fuelDataDir, "gcData") - gcxgc_file = os.path.join(gcxgc_dir, f"{fuel_name}_init.csv") - decomp_file = os.path.join(decomp_dir, f"{fuel_name}.csv") + gcxgc_file = os.path.join(FUELDATA_GC_DIR, f"{fuel_name}_init.csv") + decomp_file = os.path.join(FUELDATA_DECOMP_DIR, f"{fuel_name}.csv") if not os.path.exists(gcxgc_file): raise FileNotFoundError( - f"GCXGC file for {fuel_name} not found in {gcxgc_dir}. gxcgc_file = {gcxgc_file}" + f"GCXGC file for {fuel_name} not found in {FUELDATA_GC_DIR}. gxcgc_file = {gcxgc_file}" ) if not os.path.exists(decomp_file): raise FileNotFoundError( - f"Decomposition file for {fuel_name} not found in {decomp_dir}." + f"Decomposition file for {fuel_name} not found in {FUELDATA_DECOMP_DIR}." ) print("All required files found.") # Create the groupContribution object for the specified fuel - fuel = fl.groupContribution(fuel_name) + fuel = fl.fuel(fuel_name) # Export properties for Pele - export_converge( - fuel, - path=export_dir, - units=units, - temp_min=temp_min, - temp_max=temp_max, - ) + export_converge(fuel, export_dir, units, temp_min, temp_max, temp_step) print("\nExport completed successfully!") diff --git a/Export4Pele.py b/source/Export4Pele.py similarity index 82% rename from Export4Pele.py rename to source/Export4Pele.py index 5dda8dd..b213d8e 100644 --- a/Export4Pele.py +++ b/source/Export4Pele.py @@ -1,9 +1,16 @@ -import pandas as pd -import numpy as np import os +import sys +import numpy as np +import pandas as pd import argparse import FuelLib as fl +# Add the FuelLib directory to the Python path +FUELLIB_DIR = os.path.dirname(os.path.dirname(__file__)) +if FUELLIB_DIR not in sys.path: + sys.path.append(FUELLIB_DIR) +from paths import * + """ Script that exports critical properties and initial mass fraction data for use in Pele simulations. @@ -39,9 +46,7 @@ def vec_to_str(vec): return " ".join(f"{v}" for v in vec.values) -def export_pele( - fuel, path="sprayPropsGCM", units="mks", dep_fuel_names=None, max_dep_fuels=30 -): +def export_pele(fuel, path, units, dep_fuel_names, max_dep_fuels): """ Export fuel properties to input file for Pele simulations. @@ -49,16 +54,16 @@ def export_pele( :type fuel: groupContribution object :param path: Directory to save the input file. - :type path: str, optional + :type path: str :param units: Units for the properties ("mks" for SI, "cgs" for CGS). - :type units: str, optional + :type units: str :param dep_fuel_names: List or single fuel that each compound deposits to. - :type dep_fuel_names: str, optional + :type dep_fuel_names: str :param max_dep_fuels: Maximum number of deposition fuels to consider. - :type max_dep_fuels: int, optional + :type max_dep_fuels: int :return: None :rtype: None @@ -66,6 +71,7 @@ def export_pele( if not os.path.exists(path): os.makedirs(path) + print(f"path = {path}") # Names of the input file file_name = os.path.join(path, f"sprayPropsGCM_{fuel.name}.inp") @@ -143,11 +149,48 @@ def export_pele( "Lv_stp": ("latent", ["J/kg", "erg/g"]), } + # Get date and time for the header + from datetime import datetime + + now = datetime.now() + dt_string = now.strftime("%Y-%m-%d %H:%M:%S") + + # Get git commit hash for the header + import subprocess + + try: + git_commit = ( + subprocess.check_output(["git", "rev-parse", "HEAD"]) + .strip() + .decode("utf-8") + ) + except Exception: + git_commit = "N/A" + + # Get the remote url for the header + try: + git_remote = ( + subprocess.check_output(["git", "config", "--get", "remote.origin.url"]) + .strip() + .decode("utf-8") + ) + except Exception: + git_remote = "N/A" + # Write the properties to the input file print(f"Writing properties to {file_name}...") if os.path.exists(file_name): os.remove(file_name) with open(file_name, "a") as f: + f.write( + f"# -----------------------------------------------------------------------------\n" + f"# sprayPropsGCM_{fuel.name}.inp\n" + f"# Generated on {dt_string}\n" + f"# FuelLib remote URL: {git_remote}\n" + f"# Git commit: {git_commit}\n" + f"# Units: {units.upper()}\n" + f"# -----------------------------------------------------------------------------\n\n" + ) f.write(f"particles.spray_fuel_num = {len(fuel.compounds)}\n") f.write(f"particles.fuel_species = {vec_to_str(df['Compound'].tolist())}\n") f.write(f"particles.Y_0 = {vec_to_str(df['Y_0'].tolist())}\n") @@ -200,7 +243,7 @@ def main(): :param --max_dep_fuels: Maximum number of deposition fuels to consider. Default is 30. :type --max_dep_fuels: int, optional - :param --export_dir: Directory to export the properties. Default is "sprayPropsGCM". + :param --export_dir: Directory to export the properties. Default is "FuelLib/exportData". :type --export_dir: str, optional :raises FileNotFoundError: If required files for the specified fuel are not found. @@ -245,8 +288,8 @@ def main(): # Optional argument for export directory parser.add_argument( "--export_dir", - default="sprayPropsGCM", - help="Directory to export the properties (optional, default: sprayPropsGCM).", + default=os.path.join(FUELLIB_DIR, "exportData"), + help="Directory to export the properties (optional, default: FuelLib/exportData).", ) # Parse arguments @@ -265,32 +308,28 @@ def main(): # Check if necessary files exist in the fuelData directory print("\nChecking for required files...") - decomp_dir = os.path.join( - fl.groupContribution.fuelDataDir, "groupDecompositionData" - ) - gcxgc_dir = os.path.join(fl.groupContribution.fuelDataDir, "gcData") - gcxgc_file = os.path.join(gcxgc_dir, f"{fuel_name}_init.csv") - decomp_file = os.path.join(decomp_dir, f"{fuel_name}.csv") + gcxgc_file = os.path.join(FUELDATA_GC_DIR, f"{fuel_name}_init.csv") + decomp_file = os.path.join(FUELDATA_DECOMP_DIR, f"{fuel_name}.csv") if not os.path.exists(gcxgc_file): raise FileNotFoundError( - f"GCXGC file for {fuel_name} not found in {gcxgc_dir}. gxcgc_file = {gcxgc_file}" + f"GCXGC file for {fuel_name} not found in {FUELDATA_GC_DIR}. gxcgc_file = {gcxgc_file}" ) if not os.path.exists(decomp_file): raise FileNotFoundError( - f"Decomposition file for {fuel_name} not found in {decomp_dir}." + f"Decomposition file for {fuel_name} not found in {FUELDATA_DECOMP_DIR}." ) print("All required files found.") # Create the groupContribution object for the specified fuel - fuel = fl.groupContribution(fuel_name) + fuel = fl.fuel(fuel_name) # Export properties for Pele export_pele( fuel, - path=export_dir, - units=units, - dep_fuel_names=dep_fuel_names, - max_dep_fuels=max_dep_fuels, + export_dir, + units, + dep_fuel_names, + max_dep_fuels, ) print("\nExport completed successfully!") diff --git a/FuelLib.py b/source/FuelLib.py similarity index 93% rename from FuelLib.py rename to source/FuelLib.py index 090021c..4e5b2f4 100644 --- a/FuelLib.py +++ b/source/FuelLib.py @@ -1,10 +1,17 @@ -import pandas as pd +import os +import sys import numpy as np +import pandas as pd from scipy.optimize import curve_fit -import os +# Add the FuelLib directory to the Python path +FUELLIB_DIR = os.path.dirname(os.path.dirname(__file__)) +if FUELLIB_DIR not in sys.path: + sys.path.append(FUELLIB_DIR) +from paths import * -class groupContribution: + +class fuel: """ Class for handling group contribution calculations of thermodynamic and mixture properties. @@ -16,16 +23,6 @@ class groupContribution: :type W: int """ - # Paths to input directories - fuellibDir = os.path.dirname(os.path.abspath(__file__)) - gcmTableDir = os.path.join(fuellibDir, "gcmTableData") - fuelDataDir = os.path.join(fuellibDir, "fuelData") - groupDecompDir = os.path.join(fuelDataDir, "groupDecompositionData") - gcxgcDir = os.path.join(fuelDataDir, "gcData") - - # Path to GCM table - gcmTableFile = os.path.join(gcmTableDir, "gcmTable.csv") - # Number of first and second order groups from Constantinou and Gani N_g1 = 78 N_g2 = 43 @@ -33,15 +30,16 @@ class groupContribution: # Boltzmann's constant J/K k_B = 1.380649e-23 - def __init__(self, name, decompName=None, W=1): + def __init__(self, name, decompName=None): # Initializes the composition and calculates the GCM properties for the # specified mixture. self.name = name if decompName is None: decompName = name - groupDecompFile = os.path.join(self.groupDecompDir, f"{decompName}.csv") - gcxgcFile = os.path.join(self.gcxgcDir, f"{name}_init.csv") + groupDecompFile = os.path.join(FUELDATA_DECOMP_DIR, f"{decompName}.csv") + gcxgcFile = os.path.join(FUELDATA_GC_DIR, f"{name}_init.csv") + gcmTableFile = os.path.join(GCMTABLE_DIR, "gcmTable.csv") # Read functional group data for mixture (num_compounds,num_groups) df_Nij = pd.read_csv(groupDecompFile) @@ -93,32 +91,30 @@ def __init__(self, name, decompName=None, W=1): ) # Read and store GCM table properties - df_gcm_properties = pd.read_csv(self.gcmTableFile) - gcm_properties = df_gcm_properties.loc[ - :, ~df_gcm_properties.columns.isin(["Property", "Units"]) - ].to_numpy() + df_table = pd.read_csv(gcmTableFile) + df_table = df_table.drop(columns=["Units"]) - # Determine if approximations include second-order contributions - if W == 0 or self.num_groups < self.N_g1 + self.N_g2: - # Only retain first-order GCM properties - gcm_properties = gcm_properties[:, 0 : self.N_g1] - self.Nij = self.Nij[:, 0 : self.N_g1] + def get_row(property_name): + row = df_table[df_table["Property"] == property_name] + if row.empty: + raise ValueError(f"Property '{property_name}' not found in GCM table.") + return row.iloc[:, 1:].to_numpy().flatten() # Table data for functional groups (num_compounds,) - Tck = gcm_properties[0] # critical temperature (1) - Pck = gcm_properties[1] # critical pressure (bar) - Vck = gcm_properties[2] # critical volume (m^3/kmol) - Tbk = gcm_properties[3] # boiling temperature (1) - Tmk = gcm_properties[4] # melting point temperature (1) - hfk = gcm_properties[5] # enthalpy of formation, (kJ/mol) - gfk = gcm_properties[6] # Gibbs energy (kJ/mol) - hvk = gcm_properties[7] # latent heat of vaporization (kJ/mol) - wk = gcm_properties[8] # accentric factor (1) - Vmk = gcm_properties[9] # liquid molar volume fraction (m^3/kmol) - cpak = gcm_properties[10] # specific heat values (J/mol/K) - cpbk = gcm_properties[11] # specific heat values (J/mol/K) - cpck = gcm_properties[12] # specific heat values (J/mol/K) - mwk = gcm_properties[13] # molecular weights (g/mol) + Tck = get_row("tck") # critical temperature (1) + Pck = get_row("pck") # critical pressure (bar) + Vck = get_row("vck") # critical volume (m^3/kmol) + Tbk = get_row("tbk") # boiling temperature (1) + Tmk = get_row("tmk") # melting point temperature (1) + hfk = get_row("hfk") # enthalpy of formation, (kJ/mol) + gfk = get_row("gfk") # Gibbs energy (kJ/mol) + hvk = get_row("hvk") # latent heat of vaporization (kJ/mol) + wk = get_row("wk") # accentric factor (1) + Vmk = get_row("vmk") # liquid molar volume fraction (m^3/kmol) + cpak = get_row("CpAk") # specific heat values (J/mol/K) + cpbk = get_row("CpBk") # specific heat values (J/mol/K) + cpck = get_row("CpCk") # specific heat values (J/mol/K) + mwk = get_row("MW") # molecular weights (g/mol) # --- Compute critical properties at standard temp (num_compounds,) # Molecular weights diff --git a/tests/test_accuracy.py b/tests/test_accuracy.py index f51bd05..64189eb 100644 --- a/tests/test_accuracy.py +++ b/tests/test_accuracy.py @@ -1,17 +1,23 @@ import os +import sys import numpy as np import pandas as pd import test_functions as fxns import unittest +# Add the FuelLib directory to the Python path +FUELLIB_DIR = os.path.dirname(os.path.dirname(__file__)) +if FUELLIB_DIR not in sys.path: + sys.path.append(FUELLIB_DIR) +from paths import * +import FuelLib as fl + class CompTestCase(unittest.TestCase): """Test that accuracy is preserved""" def test_accuracy(self): """Does the PR impact the accuracy of single component fuel predictions?""" - test_dir = os.path.dirname(__file__) - baseline_dir = os.path.join(test_dir, "baselinePredictions") # Set the percentage for variation in max error max_error_diff = 1e-6 @@ -38,7 +44,7 @@ def test_accuracy(self): # Compare to NIST predictions and previous model predictions for fuel_name in fuel_names: - baseline_file = os.path.join(baseline_dir, f"{fuel_name}.csv") + baseline_file = os.path.join(TESTS_BASELINE_DIR, f"{fuel_name}.csv") df_base = pd.read_csv(baseline_file, skiprows=[1]) for prop in prop_names: diff --git a/tests/test_baseline.py b/tests/test_baseline.py index d39df48..8fc873d 100644 --- a/tests/test_baseline.py +++ b/tests/test_baseline.py @@ -1,17 +1,20 @@ +import os +import sys import numpy as np import pandas as pd -import os import test_functions as fxns """ Script for calculating baseline FuelLib mixture property predictions for CI testing Use this to update threshold values in CI test as model improves """ -import sys + # Add the FuelLib directory to the Python path -fuellib_dir = os.path.dirname(os.path.dirname(__file__)) -sys.path.append(fuellib_dir) +FUELLIB_DIR = os.path.dirname(os.path.dirname(__file__)) +if FUELLIB_DIR not in sys.path: + sys.path.append(FUELLIB_DIR) +from paths import * import FuelLib as fl # Directories for tests and baseline predictions diff --git a/tests/test_functions.py b/tests/test_functions.py index c21dd32..d8ac581 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -1,21 +1,22 @@ import os +import sys import numpy as np import pandas as pd -import sys # Add the FuelLib directory to the Python path -fuellib_dir = os.path.dirname(os.path.dirname(__file__)) -sys.path.append(fuellib_dir) +FUELLIB_DIR = os.path.dirname(os.path.dirname(__file__)) +if FUELLIB_DIR not in sys.path: + sys.path.append(FUELLIB_DIR) +from paths import * import FuelLib as fl def getPredAndData(fuel_name, prop_name): # Get the fuel properties based on the GCM - fuel = fl.groupContribution(fuel_name) + fuel = fl.fuel(fuel_name) data_file = f"{fuel_name}.csv" - dataPath = os.path.join(fuel.fuelDataDir, "propertiesData") - data = pd.read_csv(os.path.join(dataPath, data_file), skiprows=[1]) + data = pd.read_csv(os.path.join(FUELDATA_PROPS_DIR, data_file), skiprows=[1]) # Separate properties and associated temperatures from data T_data = data.Temperature[data[prop_name].notna()].to_numpy(dtype=float) diff --git a/tutorials/basic.py b/tutorials/basic.py index 3a28644..9fd432a 100644 --- a/tutorials/basic.py +++ b/tutorials/basic.py @@ -1,14 +1,14 @@ import os import sys -import numpy as np # Add the FuelLib directory to the Python path -fuellib_dir = os.path.dirname(os.path.dirname(__file__)) -sys.path.append(fuellib_dir) +FUELLIB_DIR = os.path.dirname(os.path.dirname(__file__)) +sys.path.append(FUELLIB_DIR) +import paths import FuelLib as fl -# Create a groupContribution object for the fuel "heptane-decane" -fuel = fl.groupContribution("heptane-decane") +# Create a fuel object for the fuel "heptane-decane" +fuel = fl.fuel("heptane-decane") # Display fuel name, components, initial composition, and critical temperature print(f"Fuel name: {fuel.name}") diff --git a/tutorials/compositionPlots.py b/tutorials/compositionPlots.py index dc030c0..6a1b6e0 100644 --- a/tutorials/compositionPlots.py +++ b/tutorials/compositionPlots.py @@ -1,18 +1,19 @@ +import os +import sys import numpy as np -import matplotlib.pyplot as plt import pandas as pd -import os import re -import sys +import matplotlib.pyplot as plt # Add the FuelLib directory to the Python path -fuellib_dir = os.path.dirname(os.path.dirname(__file__)) -sys.path.append(fuellib_dir) +FUELLIB_DIR = os.path.dirname(os.path.dirname(__file__)) +sys.path.append(FUELLIB_DIR) +import paths import FuelLib as fl fuel_name = "posf10325" -fuel = fl.groupContribution(fuel_name) +fuel = fl.fuel(fuel_name) # Classify compounds into families aromatic = [ diff --git a/tutorials/hefaBlends.py b/tutorials/hefaBlends.py index 212f10d..935cf12 100644 --- a/tutorials/hefaBlends.py +++ b/tutorials/hefaBlends.py @@ -1,12 +1,13 @@ -import numpy as np -import matplotlib.pyplot as plt -import pandas as pd import os import sys +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt # Add the FuelLib directory to the Python path -fuellib_dir = os.path.dirname(os.path.dirname(__file__)) -sys.path.append(fuellib_dir) +FUELLIB_DIR = os.path.dirname(os.path.dirname(__file__)) +sys.path.append(FUELLIB_DIR) +from paths import * import FuelLib as fl # ----------------------------------------------------------------------------- @@ -60,12 +61,11 @@ def getPredAndData(fuel_name, prop_name, blend): blend = np.array(blend) * 1e-2 # Convert to weight percent # Get the fuel properties based on the GCM - fuel = fl.groupContribution(fuel_name, "hefa") - jetA = fl.groupContribution(conv_fuel_name) + fuel = fl.fuel(fuel_name, "hefa") + jetA = fl.fuel(conv_fuel_name) data_file = "hefa-jet-a-blends.csv" - dataPath = os.path.join(fuel.fuelDataDir, "propertiesData") - data = pd.read_csv(os.path.join(dataPath, data_file), skiprows=[1]) + data = pd.read_csv(os.path.join(FUELDATA_PROPS_DIR, data_file), skiprows=[1]) col = f"{prop_name}_{fuel_name[5:].upper()}" prop_data = data[col] blend_data = data["HEFA_concentration"] diff --git a/tutorials/mixtureProperties.py b/tutorials/mixtureProperties.py index ea96bef..b4319fa 100644 --- a/tutorials/mixtureProperties.py +++ b/tutorials/mixtureProperties.py @@ -1,12 +1,13 @@ -import numpy as np -import matplotlib.pyplot as plt -import pandas as pd import os import sys +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt # Add the FuelLib directory to the Python path -fuellib_dir = os.path.dirname(os.path.dirname(__file__)) -sys.path.append(fuellib_dir) +FUELLIB_DIR = os.path.dirname(os.path.dirname(__file__)) +sys.path.append(FUELLIB_DIR) +from paths import * import FuelLib as fl # ----------------------------------------------------------------------------- @@ -84,11 +85,10 @@ def leglab(name): def getPredAndData(fuel_name, prop_name): # Get the fuel properties based on the GCM - fuel = fl.groupContribution(fuel_name) + fuel = fl.fuel(fuel_name) data_file = f"{fuel_name}.csv" - dataPath = os.path.join(fuel.fuelDataDir, "propertiesData") - data = pd.read_csv(os.path.join(dataPath, data_file), skiprows=[1]) + data = pd.read_csv(os.path.join(FUELDATA_PROPS_DIR, data_file), skiprows=[1]) # Separate properties and associated temperatures from data T_data = data.Temperature[data[prop_name].notna()] From f7a515c19e97606dea56ea9bcb11308ba5805b22 Mon Sep 17 00:00:00 2001 From: Dave Montgomery Date: Wed, 27 Aug 2025 12:42:26 -0600 Subject: [PATCH 2/9] Update source/Export4Pele.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- source/Export4Pele.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Export4Pele.py b/source/Export4Pele.py index b213d8e..c9938e8 100644 --- a/source/Export4Pele.py +++ b/source/Export4Pele.py @@ -50,8 +50,8 @@ def export_pele(fuel, path, units, dep_fuel_names, max_dep_fuels): """ Export fuel properties to input file for Pele simulations. - :param fuel: An instance of the groupContribution class. - :type fuel: groupContribution object + :param fuel: An instance of the fuel class. + :type fuel: fuel object :param path: Directory to save the input file. :type path: str From caa44918f01fab969202a2946eeca1da7dca8607 Mon Sep 17 00:00:00 2001 From: Dave Montgomery Date: Wed, 27 Aug 2025 12:43:38 -0600 Subject: [PATCH 3/9] Update source/Export4Converge.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- source/Export4Converge.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Export4Converge.py b/source/Export4Converge.py index 2df463a..71127ce 100644 --- a/source/Export4Converge.py +++ b/source/Export4Converge.py @@ -35,8 +35,8 @@ def export_converge(fuel, path, units, temp_min, temp_max, temp_step): """ Export mixture fuel properties to .csv for Converge simulations. - :param fuel: An instance of the groupContribution class. - :type fuel: groupContribution object + :param fuel: An instance of the fuel class. + :type fuel: fuel object :param path: Directory to save the input file. :type path: str From a501aa22a700f7443925db20bf3642da5844abbe Mon Sep 17 00:00:00 2001 From: dmontgomeryNREL Date: Wed, 27 Aug 2025 13:04:54 -0600 Subject: [PATCH 4/9] Remove W from equations --- docs/fuelprops.rst | 30 ++++++++++++++---------------- source/FuelLib.py | 30 ++++++++++++++++++------------ 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/docs/fuelprops.rst b/docs/fuelprops.rst index 49c4e4b..682616f 100644 --- a/docs/fuelprops.rst +++ b/docs/fuelprops.rst @@ -63,28 +63,26 @@ by Constantinou and Gani\ :footcite:p:`constantinou_new_1994,constantinou_estima Similarly, let :math:`\mathbf{M}` be an :math:`N_c \times N_{g_2}` matrix that specifies the number of second-order groups in each compound, where :math:`N_{g_2}` is the total number of second-order groups. The total number of groups :math:`N_g = N_{g_1} + N_{g_2} = 121`. -Define a parameter :math:`W` such that :math:`W = 0` performs a first-order group only -calculation, while :math:`W = 1` includes second-order groups. The GCM properties for -the *i-th* compound in the mixture are calculated as +The GCM properties for the *i-th* compound in the mixture are calculated as follows\ :footcite:p:`constantinou_new_1994` \ :footcite:p:`constantinou_estimation_1995` \ :footcite:p:`poling_properties_2001`: .. math:: \begin{align*} M_{w,i} &= \bigg[\sum_{k = 1}^{N_{g_1}}\mathbf{N}_{ik}m_{w1k} \bigg] \times 10^{-3}, \\ - T_{c,i} &= 181.28 \ln \bigg[ \sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} t_{c1k} + W \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} t_{c2j} \bigg],\\ - p_{c,i} &= \Bigg( \bigg[ \sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} p_{c1k} + W \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} p_{c2j} + 0.10022\bigg]^{-2} + 1.3705\Bigg)\times 10^{5}, \label{eq:gcm-pc}\\ - V_{c,i} &= \Bigg( \bigg[ \sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} v_{c1k} + W \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} v_{c2j} \bigg] -0.00435 \Bigg)\times 10^{-3}, \\ - T_{b,i} &= 204.359 \ln \bigg[ \sum_{k = 1}^{N_{g_1}} \mathbf{N}_{ik} t_{b1k} + W \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} t_{b2j}\bigg],\\ - T_{m,i} &= 102.425 \ln \bigg[ \sum_{k = 1}^{N_{g_1}} \mathbf{N}_{ik} t_{m1k} + W \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} t_{m2j}\bigg],\\ - \Delta H_{f,i} &= \Bigg( \bigg[ \sum_{k = 1}^{N_{g_1}} \mathbf{N}_{ik} h_{f1k} + W \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} h_{f2j} \bigg] + 10.835\Bigg) \times 10^3,\\ - \Delta G_{f,i} &= \Bigg( \bigg[ \sum_{k = 1}^{N_{g_1}} \mathbf{N}_{ik} g_{f1k} + W \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} g_{f2j} \bigg] -14.828 \Bigg) \times 10^3,\\ - \Delta H_{v,\textit{stp},i} &= \Bigg( \bigg[ \sum_{k = 1}^{N_{g_1}} \mathbf{N}_{ik} h_{v1k} + W \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} h_{v2j} \bigg] + 6.829\Bigg) \times 10^3, \\ - \omega_i &= 0.4085 \ln \bigg( \Big[ \sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} \omega_{1k} + W \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} \omega_{2j} + 1.1507\Big]^{1/0.5050} \bigg), \label{eq:gcm-omega}\\ - V_{m,\textit{stp},i} &= \Bigg( \bigg[ \sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} v_{m1k} + W \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} v_{m2j} \bigg] + 0.01211 \Bigg)\times 10^{-3}, \\ - C_{p,i} & =\bigg[\sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} C_{pA1_k} + W \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} C_{pA2_j} -19.7779\bigg] \nonumber \\ - & +\bigg[\sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} C_{pB1_k} + W \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} C_{pB2_j} + 22.5981\bigg] \theta \nonumber\\ - & +\bigg[\sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} C_{pC1_k} + W \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} C_{pC2_j} - 10.7983\bigg] \theta^2 \\ + T_{c,i} &= 181.28 \ln \bigg[ \sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} t_{c1k} + \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} t_{c2j} \bigg],\\ + p_{c,i} &= \Bigg( \bigg[ \sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} p_{c1k} + \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} p_{c2j} + 0.10022\bigg]^{-2} + 1.3705\Bigg)\times 10^{5}, \label{eq:gcm-pc}\\ + V_{c,i} &= \Bigg( \bigg[ \sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} v_{c1k} + \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} v_{c2j} \bigg] -0.00435 \Bigg)\times 10^{-3}, \\ + T_{b,i} &= 204.359 \ln \bigg[ \sum_{k = 1}^{N_{g_1}} \mathbf{N}_{ik} t_{b1k} + \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} t_{b2j}\bigg],\\ + T_{m,i} &= 102.425 \ln \bigg[ \sum_{k = 1}^{N_{g_1}} \mathbf{N}_{ik} t_{m1k} + \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} t_{m2j}\bigg],\\ + \Delta H_{f,i} &= \Bigg( \bigg[ \sum_{k = 1}^{N_{g_1}} \mathbf{N}_{ik} h_{f1k} + \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} h_{f2j} \bigg] + 10.835\Bigg) \times 10^3,\\ + \Delta G_{f,i} &= \Bigg( \bigg[ \sum_{k = 1}^{N_{g_1}} \mathbf{N}_{ik} g_{f1k} + \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} g_{f2j} \bigg] -14.828 \Bigg) \times 10^3,\\ + \Delta H_{v,\textit{stp},i} &= \Bigg( \bigg[ \sum_{k = 1}^{N_{g_1}} \mathbf{N}_{ik} h_{v1k} + \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} h_{v2j} \bigg] + 6.829\Bigg) \times 10^3, \\ + \omega_i &= 0.4085 \ln \bigg( \Big[ \sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} \omega_{1k} + \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} \omega_{2j} + 1.1507\Big]^{1/0.5050} \bigg), \label{eq:gcm-omega}\\ + V_{m,\textit{stp},i} &= \Bigg( \bigg[ \sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} v_{m1k} + \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} v_{m2j} \bigg] + 0.01211 \Bigg)\times 10^{-3}, \\ + C_{p,i} & =\bigg[\sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} C_{pA1_k} + \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} C_{pA2_j} -19.7779\bigg] \nonumber \\ + & +\bigg[\sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} C_{pB1_k} + \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} C_{pB2_j} + 22.5981\bigg] \theta \nonumber\\ + & +\bigg[\sum_{k=1}^{N_{g_1}} \mathbf{N}_{ik} C_{pC1_k} + \sum_{j=1}^{N_{g_2}} \mathbf{M}_{ij} C_{pC2_j} - 10.7983\bigg] \theta^2 \\ \theta &= \frac{T - 298.15}{700} \end{align*} diff --git a/source/FuelLib.py b/source/FuelLib.py index 4e5b2f4..d3d0342 100644 --- a/source/FuelLib.py +++ b/source/FuelLib.py @@ -19,8 +19,8 @@ class fuel: :type name: str :param decompName: Name of the groupDecomposition file if different from name. Defaults to None. :type decompName: str, optional - :param W: Determines whether to use first-order only approximation (W = 0). Defaults to 1. - :type W: int + :param fuelDataDir: Directory where the fuel data is stored. Defaults to FuelLib/fuelData. + :type fuelDataDir: str, optional """ # Number of first and second order groups from Constantinou and Gani @@ -30,19 +30,25 @@ class fuel: # Boltzmann's constant J/K k_B = 1.380649e-23 - def __init__(self, name, decompName=None): + def __init__(self, name, decompName=None, fuelDataDir=FUELDATA_DIR): # Initializes the composition and calculates the GCM properties for the # specified mixture. self.name = name if decompName is None: decompName = name - groupDecompFile = os.path.join(FUELDATA_DECOMP_DIR, f"{decompName}.csv") - gcxgcFile = os.path.join(FUELDATA_GC_DIR, f"{name}_init.csv") - gcmTableFile = os.path.join(GCMTABLE_DIR, "gcmTable.csv") + # Check if fuelDataDir is different from default + if fuelDataDir != FUELDATA_DIR: + FUELDATA_DIR = fuelDataDir + FUELDATA_GC_DIR = os.path.join(FUELDATA_DIR, "gcData") + FUELDATA_DECOMP_DIR = os.path.join(FUELDATA_DIR, "groupDecompositionData") + + self.groupDecompFile = os.path.join(FUELDATA_DECOMP_DIR, f"{decompName}.csv") + self.gcxgcFile = os.path.join(FUELDATA_GC_DIR, f"{name}_init.csv") + self.gcmTableFile = os.path.join(GCMTABLE_DIR, "gcmTable.csv") # Read functional group data for mixture (num_compounds,num_groups) - df_Nij = pd.read_csv(groupDecompFile) + df_Nij = pd.read_csv(self.groupDecompFile) self.Nij = df_Nij.iloc[:, 1:].to_numpy() self.num_compounds = self.Nij.shape[0] self.num_groups = self.Nij.shape[1] @@ -71,7 +77,7 @@ def __init__(self, name, decompName=None): self.fam[i] = 3 # Read initial liquid composition of mixture and normalize to get mass frac - df_gcxgc = pd.read_csv(gcxgcFile) + df_gcxgc = pd.read_csv(self.gcxgcFile) self.compounds = df_gcxgc.iloc[:, 0].to_list() self.Y_0 = df_gcxgc.iloc[:, 1].to_numpy().flatten().astype(float) self.Y_0 /= np.sum(self.Y_0) @@ -80,18 +86,18 @@ def __init__(self, name, decompName=None): if self.num_groups < self.N_g1: raise ValueError( f"Insufficient mixture description:\n" - f"The number of columns in {groupDecompFile} is less than " + f"The number of columns in {self.groupDecompFile} is less than " f"the required number of first-order groups (N_g1 = {self.N_g1})." ) if self.Y_0.shape[0] != self.num_compounds: raise ValueError( f"Insufficient mixture description:\n" - f"The number of compounds in {groupDecompFile} does not " - f"equal the number of compounds in {gcxgcFile}." + f"The number of compounds in {self.groupDecompFile} does not " + f"equal the number of compounds in {self.gcxgcFile}." ) # Read and store GCM table properties - df_table = pd.read_csv(gcmTableFile) + df_table = pd.read_csv(self.gcmTableFile) df_table = df_table.drop(columns=["Units"]) def get_row(property_name): From 7a4d6c2c2d3c83ec68da6fc4ced6e54c2d1e77ca Mon Sep 17 00:00:00 2001 From: dmontgomeryNREL Date: Wed, 27 Aug 2025 13:12:11 -0600 Subject: [PATCH 5/9] Fix error --- source/FuelLib.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/source/FuelLib.py b/source/FuelLib.py index d3d0342..f1b5e7e 100644 --- a/source/FuelLib.py +++ b/source/FuelLib.py @@ -37,14 +37,17 @@ def __init__(self, name, decompName=None, fuelDataDir=FUELDATA_DIR): self.name = name if decompName is None: decompName = name - # Check if fuelDataDir is different from default if fuelDataDir != FUELDATA_DIR: - FUELDATA_DIR = fuelDataDir - FUELDATA_GC_DIR = os.path.join(FUELDATA_DIR, "gcData") - FUELDATA_DECOMP_DIR = os.path.join(FUELDATA_DIR, "groupDecompositionData") + self.fuelDataDir = fuelDataDir + self.fuelDataGcDir = os.path.join(self.fuelDataDir, "gcData") + self.fuelDataDecompDir = os.path.join(self.fuelDataDir, "groupDecompositionData") + else: + self.fuelDataDir = FUELDATA_DIR + self.fuelDataGcDir = FUELDATA_GC_DIR + self.fuelDataDecompDir = FUELDATA_DECOMP_DIR - self.groupDecompFile = os.path.join(FUELDATA_DECOMP_DIR, f"{decompName}.csv") - self.gcxgcFile = os.path.join(FUELDATA_GC_DIR, f"{name}_init.csv") + self.groupDecompFile = os.path.join(self.fuelDataDecompDir, f"{decompName}.csv") + self.gcxgcFile = os.path.join(self.fuelDataGcDir, f"{name}_init.csv") self.gcmTableFile = os.path.join(GCMTABLE_DIR, "gcmTable.csv") # Read functional group data for mixture (num_compounds,num_groups) From 5c9932dd573d0c321a75785155147d0d6daa2ac1 Mon Sep 17 00:00:00 2001 From: dmontgomeryNREL Date: Wed, 27 Aug 2025 13:12:43 -0600 Subject: [PATCH 6/9] Formatting --- source/FuelLib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/FuelLib.py b/source/FuelLib.py index f1b5e7e..8108b87 100644 --- a/source/FuelLib.py +++ b/source/FuelLib.py @@ -40,7 +40,9 @@ def __init__(self, name, decompName=None, fuelDataDir=FUELDATA_DIR): if fuelDataDir != FUELDATA_DIR: self.fuelDataDir = fuelDataDir self.fuelDataGcDir = os.path.join(self.fuelDataDir, "gcData") - self.fuelDataDecompDir = os.path.join(self.fuelDataDir, "groupDecompositionData") + self.fuelDataDecompDir = os.path.join( + self.fuelDataDir, "groupDecompositionData" + ) else: self.fuelDataDir = FUELDATA_DIR self.fuelDataGcDir = FUELDATA_GC_DIR From 21890d340bc977a762b58194fc2c4c701b9a98dd Mon Sep 17 00:00:00 2001 From: dmontgomeryNREL Date: Wed, 27 Aug 2025 13:34:35 -0600 Subject: [PATCH 7/9] Remove debug print statement --- source/Export4Pele.py | 1 - 1 file changed, 1 deletion(-) diff --git a/source/Export4Pele.py b/source/Export4Pele.py index c9938e8..af9a36e 100644 --- a/source/Export4Pele.py +++ b/source/Export4Pele.py @@ -71,7 +71,6 @@ def export_pele(fuel, path, units, dep_fuel_names, max_dep_fuels): if not os.path.exists(path): os.makedirs(path) - print(f"path = {path}") # Names of the input file file_name = os.path.join(path, f"sprayPropsGCM_{fuel.name}.inp") From d1a7fbf024e92afd07eebb4f3667506c8dc80bb9 Mon Sep 17 00:00:00 2001 From: dmontgomeryNREL Date: Wed, 27 Aug 2025 13:51:54 -0600 Subject: [PATCH 8/9] Keep optional args in export fxns --- source/Export4Converge.py | 14 +++++++------- source/Export4Pele.py | 18 +++++++++--------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/source/Export4Converge.py b/source/Export4Converge.py index 71127ce..bf7fe57 100644 --- a/source/Export4Converge.py +++ b/source/Export4Converge.py @@ -31,7 +31,7 @@ """ -def export_converge(fuel, path, units, temp_min, temp_max, temp_step): +def export_converge(fuel, path=os.path.join(FUELLIB_DIR, "exportData"), units="mks", temp_min=0, temp_max=1000, temp_step=10): """ Export mixture fuel properties to .csv for Converge simulations. @@ -39,19 +39,19 @@ def export_converge(fuel, path, units, temp_min, temp_max, temp_step): :type fuel: fuel object :param path: Directory to save the input file. - :type path: str + :type path: str, optional (default: FuelLib/exportData) :param units: Units for the properties ("mks" for SI, "cgs" for CGS). - :type units: str + :type units: str, optional (default: "mks") :param temp_min: Minimum temperature (K) for the property calculations. - :type temp_min: float + :type temp_min: float, optional (default: 0) :param temp_max: Maximum temperature (K)for the property calculations. - :type temp_max: float + :type temp_max: float, optional (default: 1000) :param temp_step: Step size for temperature (K). - :type temp_step: float + :type temp_step: int, optional (default: 10) :return: None :rtype: None @@ -345,7 +345,7 @@ def main(): fuel = fl.fuel(fuel_name) # Export properties for Pele - export_converge(fuel, export_dir, units, temp_min, temp_max, temp_step) + export_converge(fuel, path=export_dir, units=units, temp_min=temp_min, temp_max=temp_max, temp_step=temp_step) print("\nExport completed successfully!") diff --git a/source/Export4Pele.py b/source/Export4Pele.py index af9a36e..2cd82e5 100644 --- a/source/Export4Pele.py +++ b/source/Export4Pele.py @@ -46,7 +46,7 @@ def vec_to_str(vec): return " ".join(f"{v}" for v in vec.values) -def export_pele(fuel, path, units, dep_fuel_names, max_dep_fuels): +def export_pele(fuel, path=os.path.join(FUELLIB_DIR, "exportData"), units="mks", dep_fuel_names=None, max_dep_fuels=30): """ Export fuel properties to input file for Pele simulations. @@ -54,16 +54,16 @@ def export_pele(fuel, path, units, dep_fuel_names, max_dep_fuels): :type fuel: fuel object :param path: Directory to save the input file. - :type path: str + :type path: str, optional (default: FuelLib/exportData) :param units: Units for the properties ("mks" for SI, "cgs" for CGS). - :type units: str + :type units: str, optional (default: "mks") :param dep_fuel_names: List or single fuel that each compound deposits to. - :type dep_fuel_names: str + :type dep_fuel_names: list of str, optional (default: None) :param max_dep_fuels: Maximum number of deposition fuels to consider. - :type max_dep_fuels: int + :type max_dep_fuels: int, optional (default: 30) :return: None :rtype: None @@ -325,10 +325,10 @@ def main(): # Export properties for Pele export_pele( fuel, - export_dir, - units, - dep_fuel_names, - max_dep_fuels, + path=export_dir, + units=units, + dep_fuel_names=dep_fuel_names, + max_dep_fuels=max_dep_fuels, ) print("\nExport completed successfully!") From 2564fee120c2bbdda0a460bcbd57544204f11ead Mon Sep 17 00:00:00 2001 From: dmontgomeryNREL Date: Wed, 27 Aug 2025 13:53:31 -0600 Subject: [PATCH 9/9] Format --- source/Export4Converge.py | 18 ++++++++++++++++-- source/Export4Pele.py | 8 +++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/source/Export4Converge.py b/source/Export4Converge.py index bf7fe57..6763f90 100644 --- a/source/Export4Converge.py +++ b/source/Export4Converge.py @@ -31,7 +31,14 @@ """ -def export_converge(fuel, path=os.path.join(FUELLIB_DIR, "exportData"), units="mks", temp_min=0, temp_max=1000, temp_step=10): +def export_converge( + fuel, + path=os.path.join(FUELLIB_DIR, "exportData"), + units="mks", + temp_min=0, + temp_max=1000, + temp_step=10, +): """ Export mixture fuel properties to .csv for Converge simulations. @@ -345,7 +352,14 @@ def main(): fuel = fl.fuel(fuel_name) # Export properties for Pele - export_converge(fuel, path=export_dir, units=units, temp_min=temp_min, temp_max=temp_max, temp_step=temp_step) + export_converge( + fuel, + path=export_dir, + units=units, + temp_min=temp_min, + temp_max=temp_max, + temp_step=temp_step, + ) print("\nExport completed successfully!") diff --git a/source/Export4Pele.py b/source/Export4Pele.py index 2cd82e5..74e8879 100644 --- a/source/Export4Pele.py +++ b/source/Export4Pele.py @@ -46,7 +46,13 @@ def vec_to_str(vec): return " ".join(f"{v}" for v in vec.values) -def export_pele(fuel, path=os.path.join(FUELLIB_DIR, "exportData"), units="mks", dep_fuel_names=None, max_dep_fuels=30): +def export_pele( + fuel, + path=os.path.join(FUELLIB_DIR, "exportData"), + units="mks", + dep_fuel_names=None, + max_dep_fuels=30, +): """ Export fuel properties to input file for Pele simulations.