diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 405cddb1f1..a5f3857d2f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -133,14 +133,14 @@ jobs: python-version: 3.11 if: matrix.python-version == '3.11' - name: Install Brew dependencies - run: brew install boost libomp hdf5 + run: brew install --display-times boost libomp hdf5 - name: Setup Homebrew Python # This path should work for future Python versions as well if: matrix.python-version != '3.11' run: | - brew install python@${{ matrix.python-version }} + brew install --display-times python@${{ matrix.python-version }} brew link --force --overwrite python@${{ matrix.python-version }} - brew install scons + brew install --display-times scons - name: Upgrade pip run: $PYTHON_CMD -m pip install -U pip 'setuptools>=47.0.0,<48' wheel - name: Install Python dependencies @@ -384,6 +384,7 @@ jobs: - name: Run the examples # See https://unix.stackexchange.com/a/392973 for an explanation of the -exec part run: | + export LD_LIBRARY_PATH=build/lib find samples/python -type f -iname "*.py" \ -exec sh -c 'for n; do echo "$n" | tee -a results.txt && python3 "$n" >> results.txt || exit 1; done' sh {} + env: @@ -562,7 +563,7 @@ jobs: id: cache-boost with: path: ${{env.BOOST_ROOT}} - key: boost + key: boost-178-win - name: Install Boost Headers if: steps.cache-boost.outputs.cache-hit != 'true' @@ -680,9 +681,9 @@ jobs: python3 `which scons` test show_long_tests=yes verbose_tests=yes --debug=time windows-mingw: - name: mingw on Windows, Python 3.8 - runs-on: windows-2019 - timeout-minutes: 60 + name: mingw on Windows, Python 3.10 + runs-on: windows-2022 + timeout-minutes: 120 # MinGW is slooooow env: BOOST_ROOT: ${{github.workspace}}/3rdparty/boost BOOST_URL: https://boostorg.jfrog.io/artifactory/main/release/1.78.0/source/boost_1_78_0.7z @@ -694,22 +695,23 @@ jobs: - name: Set Up Python uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: "3.10" architecture: x64 - name: Install Python dependencies run: | - python -m pip install -U pip 'setuptools>=47.0.0,<48' wheel + python -m pip install -U pip setuptools wheel python -m pip install scons pypiwin32 numpy ruamel.yaml cython h5py pandas pytest pytest-github-actions-annotate-failures - name: Restore Boost cache uses: actions/cache@v3 id: cache-boost with: path: ${{env.BOOST_ROOT}} - key: boost + key: boost-178-win - name: Set up MinGW uses: egor-tensin/setup-mingw@v2 with: platform: x64 + static: false - name: Install Boost Headers if: steps.cache-boost.outputs.cache-hit != 'true' run: | @@ -722,11 +724,26 @@ jobs: shell: bash - name: Build Cantera run: scons build -j2 boost_inc_dir=%BOOST_ROOT% debug=n logging=debug - python_package=full env_vars=PYTHONPATH,GITHUB_ACTIONS + python_package=full env_vars=USERPROFILE,PYTHONPATH,GITHUB_ACTIONS toolchain=mingw f90_interface=n --debug=time shell: cmd + - name: Upload Wheel + uses: actions/upload-artifact@v3 + with: + path: build\python\dist\Cantera*.whl + name: Cantera-win_amd64.whl + retention-days: 2 - name: Test Cantera run: scons test show_long_tests=yes verbose_tests=yes --debug=time + - name: Upload Test binaries + if: always() + uses: actions/upload-artifact@v3 + with: + path: | + build/test/**/*.exe + build/lib/*.dll + name: mingw-gtest-binaries + retention-days: 2 dotnet: name: .NET on ${{ matrix.os }} @@ -755,7 +772,7 @@ jobs: shell: pwsh if: matrix.os == 'windows-2022' - name: Install Brew dependencies (macOS) - run: brew install hdf5 + run: brew install --display-times hdf5 if: matrix.os == 'macos-11' - name: Install Apt dependencies (Ubuntu) run: sudo apt install libhdf5-dev diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a604b7e9af..0d0a0b2297 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,8 +79,8 @@ * Class names use `InitialCapsNames` * Methods use `camelCaseNames` * Do not indent the contents of namespaces -* Code should follow the C++14 standard, with minimum required compiler versions - GCC 6.1, Clang 3.4, MSVC 14.1 (2017) and Intel 17.0. +* Code should follow the C++17 standard, with minimum required compiler versions + GCC 7.0, Clang 4.0, MSVC 14.14 (Visual Studio 2017 version 15.7) and Intel 19.0. * Avoid manual memory management (that is, `new` and `delete`), preferring to use standard library containers, as well as `std::unique_ptr` and `std::shared_ptr` when dynamic allocation is required. diff --git a/SConstruct b/SConstruct index 4189131163..9f34880ad7 100644 --- a/SConstruct +++ b/SConstruct @@ -219,8 +219,8 @@ config_options = [ options with spaces, for example, "cxx_flags='-g -Wextra -O3 --std=c++14'" """, { - "cl": "/EHsc", - "default": "-std=c++14" + "cl": "/EHsc /std:c++17", + "default": "-std=c++17" }), Option( "CC", @@ -1131,9 +1131,6 @@ if env['coverage']: logger.error("Coverage testing is only available with GCC.") exit(0) -if env['toolchain'] == 'mingw': - env.Append(LINKFLAGS=['-static-libgcc', '-static-libstdc++']) - def config_error(message): if env["logging"].lower() == "debug": logger.error(message) @@ -1900,13 +1897,20 @@ if env["python_package"] == "full" and env["OS"] == "Darwin": # the name of the wheel file for the Python module. If this is not specified by the # MACOSX_DEPLOYMENT_TARGET environment variable, get the value from the Python # installation and use that. - if not env["ENV"].get("MACOSX_DEPLOYMENT_TARGET", False): + mac_target = env["ENV"].get("MACOSX_DEPLOYMENT_TARGET", None) + if not mac_target: info = get_command_output( env["python_cmd"], "-c", "import sysconfig; print(sysconfig.get_platform())" ) - env["ENV"]["MACOSX_DEPLOYMENT_TARGET"] = info.split("-")[1] + mac_target = info.split("-")[1] + if parse_version(mac_target) < parse_version('10.15'): + # macOS 10.15 is the minimum version with C++17 support + mac_target = '10.15' + + env["ENV"]["MACOSX_DEPLOYMENT_TARGET"] = mac_target + logger.info(f"MACOSX_DEPLOYMENT_TARGET = {mac_target}") # Matlab Toolbox settings if env["matlab_path"] != "" and env["matlab_toolbox"] == "default": @@ -2087,6 +2091,9 @@ else: for loc in locations: env[f"inst_{loc}"] = env[f"ct_{loc}"].replace(env["ct_installroot"], instRoot) +if env['use_rpath_linkage']: + env.Append(RPATH=env['ct_libdir']) + # ************************************** # *** Set options needed in config.h *** # ************************************** @@ -2189,7 +2196,15 @@ def install(*args, **kwargs): env.SConsignFile() env.Prepend(CPPPATH=[], - LIBPATH=[Dir('build/lib')]) + LIBPATH=[Dir('build/lib')]) + +# Add build/lib to library search path in order to find Cantera shared library +if env['OS'] == 'Windows': + env.PrependENVPath('PATH', Dir('#build/lib').abspath) +elif env['OS'] == 'Darwin': + env.PrependENVPath('DYLD_LIBRARY_PATH', Dir('#build/lib').abspath) +else: + env.PrependENVPath('LD_LIBRARY_PATH', Dir('#build/lib').abspath) for yaml in multi_glob(env, "data", "yaml"): dest = Path() / "build" / "data" / yaml.name @@ -2219,6 +2234,9 @@ else: env["external_libs"] = [] env["external_libs"].extend(env["sundials_libs"]) +if env["OS"] == "Linux": + env["external_libs"].append("dl") + if env["use_hdf5"]: env["external_libs"].append("hdf5") diff --git a/ext/SConscript b/ext/SConscript index eb1073e998..81c664a752 100644 --- a/ext/SConscript +++ b/ext/SConscript @@ -40,8 +40,7 @@ if not env['system_fmt']: license_files["fmtlib"] = File("#ext/fmt/LICENSE.rst") localenv = prep_default(env) localenv.Prepend(CPPPATH=Dir('#ext/fmt/include')) - libraryTargets.extend( - localenv.SharedObject(multi_glob(localenv, 'fmt/src', 'cc'))) + libraryTargets.extend(localenv.SharedObject(['fmt/src/format.cc', 'fmt/src/os.cc'])) for name in ('format.h', 'ostream.h', 'printf.h', 'core.h', 'format-inl.h'): ext_copies.extend( copyenv.Command("#include/cantera/ext/fmt/" + name, diff --git a/ext/eigen b/ext/eigen index 21ae2afd4e..3147391d94 160000 --- a/ext/eigen +++ b/ext/eigen @@ -1 +1 @@ -Subproject commit 21ae2afd4edaa1b69782c67a54182d34efe43f9c +Subproject commit 3147391d946bb4b6c68edd901f2add6ac1f31f8c diff --git a/ext/fmt b/ext/fmt index 19bd751020..a33701196a 160000 --- a/ext/fmt +++ b/ext/fmt @@ -1 +1 @@ -Subproject commit 19bd751020a1f3c3363b2eb67a039852f139a8d3 +Subproject commit a33701196adfad74917046096bf5a2aa0ab0bb50 diff --git a/include/cantera/base/ExtensionManager.h b/include/cantera/base/ExtensionManager.h index b769f5fa1b..4d9b34b2a6 100644 --- a/include/cantera/base/ExtensionManager.h +++ b/include/cantera/base/ExtensionManager.h @@ -44,6 +44,31 @@ class ExtensionManager throw NotImplementedError("ExtensionManager::registerRateBuilders"); }; + //! Register a user-defined ReactionRate implementation with ReactionRateFactory + //! @param extensionName The name of the library/module containing the user-defined + //! rate. For example, the module name for rates implemented in Python. + //! @param className The name of the rate in the user's code. For example, the + //! Python class name + //! @param rateName The name used to construct a rate of this type using + //! the newReactionRate() function or from a YAML input file + virtual void registerRateBuilder(const string& extensionName, + const string& className, const string& rateName) + { + throw NotImplementedError("ExtensionManager::registerRateBuilder"); + } + + //! Register a user-defined ReactionData implementation + //! @param extensionName The name of the library/module containing the user-defined + //! type. For example, the module name for rates implemented in Python. + //! @param className The name of the data object in the user's code. For example, + //! the Python class name + //! @param rateName The name of the corresponding reaction rate type + virtual void registerRateDataBuilder(const string& extensionName, + const string& className, const string& rateName) + { + throw NotImplementedError("ExtensionManager::registerRateDataBuilder"); + } + //! Create an object in an external language that wraps the specified ReactionData //! object //! diff --git a/include/cantera/base/ExtensionManagerFactory.h b/include/cantera/base/ExtensionManagerFactory.h index 77b48fb873..03bd58afae 100644 --- a/include/cantera/base/ExtensionManagerFactory.h +++ b/include/cantera/base/ExtensionManagerFactory.h @@ -26,11 +26,11 @@ class ExtensionManagerFactory : public Factory //! Delete the static instance of this factory virtual void deleteFactory(); -private: //! Static function that returns the static instance of the factory, creating it //! if necessary. static ExtensionManagerFactory& factory(); +private: //! static member of the single factory instance static ExtensionManagerFactory* s_factory; diff --git a/include/cantera/base/ct_defs.h b/include/cantera/base/ct_defs.h index af3aae6e1d..8ec1103301 100644 --- a/include/cantera/base/ct_defs.h +++ b/include/cantera/base/ct_defs.h @@ -22,9 +22,11 @@ #include #include #include +#include #include #include #include +#include /** * Namespace for the Cantera kernel. @@ -36,6 +38,12 @@ using std::shared_ptr; using std::make_shared; using std::unique_ptr; using std::isnan; // workaround for bug in libstdc++ 4.8 +using std::string; +using std::vector; +using std::map; +using std::set; +using std::function; +using std::pair; /*! * All physical constants are stored here. @@ -159,20 +167,20 @@ const double Tiny = 1.e-20; /*! * This is used mostly to assign concentrations and mole fractions to species. */ -typedef std::map compositionMap; +typedef map compositionMap; //! Map from string names to doubles. Used for defining species mole/mass //! fractions, elemental compositions, and reaction stoichiometries. -typedef std::map Composition; +typedef map Composition; //! Turn on the use of stl vectors for the basic array type within cantera //! Vector of doubles. -typedef std::vector vector_fp; +typedef vector vector_fp; //! Vector of ints -typedef std::vector vector_int; +typedef vector vector_int; //! A grouplist is a vector of groups of species -typedef std::vector > grouplist_t; +typedef vector> grouplist_t; //! index returned by functions to indicate "no position" const size_t npos = static_cast(-1); diff --git a/include/cantera/base/global.h b/include/cantera/base/global.h index 4021d8513b..be53d7441e 100644 --- a/include/cantera/base/global.h +++ b/include/cantera/base/global.h @@ -87,6 +87,14 @@ void loadExtension(const std::string& extType, const std::string& name); //! @since New in Cantera 3.0 void loadExtensions(const AnyMap& node); +//! @copydoc Application::searchPythonVersions +void searchPythonVersions(const string& versions); + +//! Returns `true` if Cantera was loaded as a shared library in the current +//! application. Returns `false` if it was statically linked. +//! @since New in Cantera 3.0 +bool usingSharedLibrary(); + //! Delete and free all memory associated with the application /*! * Delete all global data. It should be called at the end of the @@ -97,6 +105,11 @@ void appdelete(); //! @copydoc Application::thread_complete void thread_complete(); +//! Returns the Cantera version. This function when needing to access the version from a +//! library, rather than the `CANTERA_VERSION` macro that is available at compile time. +//! @since New in Cantera 3.0 +string version(); + //! Returns the hash of the git commit from which Cantera was compiled, if known std::string gitCommit(); diff --git a/include/cantera/clib/clib_defs.h b/include/cantera/clib/clib_defs.h index 35d1bd2608..2879f4e0ce 100644 --- a/include/cantera/clib/clib_defs.h +++ b/include/cantera/clib/clib_defs.h @@ -11,21 +11,10 @@ #include "cantera/base/config.h" #include -#ifdef _WIN32 -// Windows (MSVC or MinGW) -# ifdef CANTERA_USE_INTERNAL -# define CANTERA_CAPI extern __declspec(dllexport) -# else -# define CANTERA_CAPI extern __declspec(dllimport) -# endif -#else -// Non-Windows platform -# ifdef CANTERA_USE_INTERNAL -# define CANTERA_CAPI extern -# else -# define CANTERA_CAPI -# endif -#endif +// Legacy attribute applied to clib functions. Currently, used only to identify +// functions that should be considered by the 'sourcegen' parser for inclusion in the +// C# interface. +#define CANTERA_CAPI // Values returned for error conditions #ifndef ERR diff --git a/include/cantera/cython/funcWrapper.h b/include/cantera/cython/funcWrapper.h index 1457cdc259..554702456c 100644 --- a/include/cantera/cython/funcWrapper.h +++ b/include/cantera/cython/funcWrapper.h @@ -7,9 +7,6 @@ #include "cantera/numerics/Func1.h" #include "cantera/base/ctexceptions.h" #include - -#define CANTERA_USE_INTERNAL -#include "cantera/clib/clib_defs.h" #include "Python.h" typedef double(*callback_wrapper)(double, void*, void**); @@ -166,7 +163,7 @@ class Func1Py : public Cantera::Func1 }; extern "C" { - CANTERA_CAPI PyObject* pyCanteraError; + extern PyObject* pyCanteraError; } diff --git a/include/cantera/cython/utils_utils.h b/include/cantera/cython/utils_utils.h index b3d16aabf4..70d3c01cf4 100644 --- a/include/cantera/cython/utils_utils.h +++ b/include/cantera/cython/utils_utils.h @@ -25,12 +25,23 @@ static std::map mapped_PyWarnings = { {"User", PyExc_UserWarning} }; -// Wrappers for preprocessor defines -inline std::string get_cantera_version() +// Cantera version for Python module compilation +inline std::string get_cantera_version_py() +{ + return CANTERA_VERSION; +} + +// Git commit for Python module compilation +inline std::string get_cantera_git_commit_py() { - return std::string(CANTERA_VERSION); +#ifdef GIT_COMMIT + return GIT_COMMIT; +#else + return "unknown"; +#endif } +// Wrappers for preprocessor defines inline int get_sundials_version() { return CT_SUNDIALS_VERSION; diff --git a/include/cantera/extensions/PythonExtensionManager.h b/include/cantera/extensions/PythonExtensionManager.h index 4a068ced83..8be5bddb8b 100644 --- a/include/cantera/extensions/PythonExtensionManager.h +++ b/include/cantera/extensions/PythonExtensionManager.h @@ -29,13 +29,13 @@ class PythonExtensionManager : public ExtensionManager PythonExtensionManager(); virtual void registerRateBuilders(const std::string& extensionName) override; - //! Function called from Cython to register an ExtensibleRate implementation - static void registerPythonRateBuilder(const std::string& moduleName, - const std::string& className, const std::string& rateName); + void registerRateBuilder(const string& moduleName, + const string& className, const string& rateName) override; - //! Function called from Cython to register an ExtensibleRateData implementation - static void registerPythonRateDataBuilder(const std::string& moduleName, - const std::string& className, const std::string& rateName); + static void registerSelf(); + + void registerRateDataBuilder(const string& moduleName, + const string& className, const string& rateName) override; private: static bool s_imported; diff --git a/include/cantera/kinetics/Arrhenius.h b/include/cantera/kinetics/Arrhenius.h index 1cbd360900..f311ec3a4c 100644 --- a/include/cantera/kinetics/Arrhenius.h +++ b/include/cantera/kinetics/Arrhenius.h @@ -64,16 +64,10 @@ class ArrheniusBase : public ReactionRate ArrheniusBase(double A, double b, double Ea); //! Constructor based on AnyValue content - ArrheniusBase(const AnyValue& rate, - const UnitSystem& units, const UnitStack& rate_units) - { - setRateParameters(rate, units, rate_units); - } + ArrheniusBase(const AnyValue& rate, const UnitSystem& units, + const UnitStack& rate_units); - explicit ArrheniusBase(const AnyMap& node, const UnitStack& rate_units={}) - { - setParameters(node, rate_units); - } + explicit ArrheniusBase(const AnyMap& node, const UnitStack& rate_units={}); //! Perform object setup based on AnyValue node information /*! diff --git a/include/cantera/kinetics/BlowersMaselRate.h b/include/cantera/kinetics/BlowersMaselRate.h index 0f8f926f81..684619965d 100644 --- a/include/cantera/kinetics/BlowersMaselRate.h +++ b/include/cantera/kinetics/BlowersMaselRate.h @@ -84,11 +84,8 @@ class BlowersMaselRate : public ArrheniusBase */ BlowersMaselRate(double A, double b, double Ea0, double w); - explicit BlowersMaselRate(const AnyMap& node, const UnitStack& rate_units={}) - : BlowersMaselRate() - { - setParameters(node, rate_units); - } + explicit BlowersMaselRate(const AnyMap& node, + const UnitStack& rate_units={}); unique_ptr newMultiRate() const override { return unique_ptr(new MultiRate); diff --git a/include/cantera/kinetics/ChebyshevRate.h b/include/cantera/kinetics/ChebyshevRate.h index a24b4daf7c..e08dd64b2f 100644 --- a/include/cantera/kinetics/ChebyshevRate.h +++ b/include/cantera/kinetics/ChebyshevRate.h @@ -105,11 +105,7 @@ class ChebyshevRate final : public ReactionRate ChebyshevRate(double Tmin, double Tmax, double Pmin, double Pmax, const Array2D& coeffs); - ChebyshevRate(const AnyMap& node, const UnitStack& rate_units={}) - : ChebyshevRate() - { - setParameters(node, rate_units); - } + ChebyshevRate(const AnyMap& node, const UnitStack& rate_units={}); unique_ptr newMultiRate() const { return unique_ptr( diff --git a/include/cantera/kinetics/Custom.h b/include/cantera/kinetics/Custom.h index 95127188d3..779da6e810 100644 --- a/include/cantera/kinetics/Custom.h +++ b/include/cantera/kinetics/Custom.h @@ -37,11 +37,7 @@ class CustomFunc1Rate final : public ReactionRate { public: CustomFunc1Rate(); - CustomFunc1Rate(const AnyMap& node, const UnitStack& rate_units) - : CustomFunc1Rate() - { - setParameters(node, rate_units); - } + CustomFunc1Rate(const AnyMap& node, const UnitStack& rate_units); unique_ptr newMultiRate() const override { return unique_ptr(new MultiRate); diff --git a/include/cantera/kinetics/Falloff.h b/include/cantera/kinetics/Falloff.h index db2592c7b1..b22698c88d 100644 --- a/include/cantera/kinetics/Falloff.h +++ b/include/cantera/kinetics/Falloff.h @@ -86,11 +86,7 @@ class FalloffRate : public ReactionRate { } - FalloffRate(const AnyMap& node, const UnitStack& rate_units={}) - : FalloffRate() - { - setParameters(node, rate_units); - } + FalloffRate(const AnyMap& node, const UnitStack& rate_units={}); /** * Initialize. Must be called before any other method is invoked. @@ -199,7 +195,7 @@ class FalloffRate : public ReactionRate //! Evaluate reaction rate //! @param shared_data data shared by all reactions of a given type - virtual double evalFromStruct(const FalloffData& shared_data) { + double evalFromStruct(const FalloffData& shared_data) { updateTemp(shared_data.temperature, m_work.data()); m_rc_low = m_lowRate.evalRate(shared_data.logT, shared_data.recipT); m_rc_high = m_highRate.evalRate(shared_data.logT, shared_data.recipT); @@ -286,20 +282,10 @@ class LindemannRate final : public FalloffRate public: LindemannRate() = default; - LindemannRate(const AnyMap& node, const UnitStack& rate_units={}) - : LindemannRate() - { - setParameters(node, rate_units); - } + LindemannRate(const AnyMap& node, const UnitStack& rate_units={}); - LindemannRate( - const ArrheniusRate& low, const ArrheniusRate& high, const vector_fp& c) - : LindemannRate() - { - m_lowRate = low; - m_highRate = high; - setFalloffCoeffs(c); - } + LindemannRate(const ArrheniusRate& low, const ArrheniusRate& high, + const vector_fp& c); unique_ptr newMultiRate() const override{ return unique_ptr( @@ -348,19 +334,9 @@ class TroeRate final : public FalloffRate m_work.resize(1); } - TroeRate(const AnyMap& node, const UnitStack& rate_units={}) - : TroeRate() - { - setParameters(node, rate_units); - } - - TroeRate(const ArrheniusRate& low, const ArrheniusRate& high, const vector_fp& c) - : TroeRate() - { - m_lowRate = low; - m_highRate = high; - setFalloffCoeffs(c); - } + TroeRate(const AnyMap& node, const UnitStack& rate_units={}); + TroeRate(const ArrheniusRate& low, const ArrheniusRate& high, + const vector_fp& c); unique_ptr newMultiRate() const override { return unique_ptr(new MultiRate); @@ -451,11 +427,7 @@ class SriRate final : public FalloffRate m_work.resize(2); } - SriRate(const AnyMap& node, const UnitStack& rate_units={}) - : SriRate() - { - setParameters(node, rate_units); - } + SriRate(const AnyMap& node, const UnitStack& rate_units={}); SriRate(const ArrheniusRate& low, const ArrheniusRate& high, const vector_fp& c) : SriRate() @@ -562,11 +534,7 @@ class TsangRate final : public FalloffRate m_work.resize(1); } - TsangRate(const AnyMap& node, const UnitStack& rate_units={}) - : TsangRate() - { - setParameters(node, rate_units); - } + TsangRate(const AnyMap& node, const UnitStack& rate_units={}); TsangRate(const ArrheniusRate& low, const ArrheniusRate& high, const vector_fp& c) : TsangRate() diff --git a/include/cantera/kinetics/KineticsFactory.h b/include/cantera/kinetics/KineticsFactory.h index 6e03128a84..ff62f2163e 100644 --- a/include/cantera/kinetics/KineticsFactory.h +++ b/include/cantera/kinetics/KineticsFactory.h @@ -21,19 +21,9 @@ namespace Cantera class KineticsFactory : public Factory { public: - static KineticsFactory* factory() { - std::unique_lock lock(kinetics_mutex); - if (!s_factory) { - s_factory = new KineticsFactory; - } - return s_factory; - } + static KineticsFactory* factory(); - virtual void deleteFactory() { - std::unique_lock lock(kinetics_mutex); - delete s_factory; - s_factory = 0; - } + virtual void deleteFactory(); /** * Return a new, empty kinetics manager. @@ -49,19 +39,12 @@ class KineticsFactory : public Factory /** * Create a new kinetics manager. */ -inline Kinetics* newKineticsMgr(const std::string& model) -{ - return KineticsFactory::factory()->newKinetics(model); -} +Kinetics* newKineticsMgr(const string& model); /** * Create a new Kinetics instance. */ -inline shared_ptr newKinetics(const std::string& model) -{ - shared_ptr kin(KineticsFactory::factory()->newKinetics(model)); - return kin; -} +shared_ptr newKinetics(const string& model); /*! * Create a new kinetics manager, initialize it, and add reactions diff --git a/include/cantera/kinetics/PlogRate.h b/include/cantera/kinetics/PlogRate.h index 287d1ab2d3..8db8900155 100644 --- a/include/cantera/kinetics/PlogRate.h +++ b/include/cantera/kinetics/PlogRate.h @@ -81,9 +81,7 @@ class PlogRate final : public ReactionRate //! Constructor from Arrhenius rate expressions at a set of pressures explicit PlogRate(const std::multimap& rates); - PlogRate(const AnyMap& node, const UnitStack& rate_units={}) : PlogRate() { - setParameters(node, rate_units); - } + PlogRate(const AnyMap& node, const UnitStack& rate_units={}); unique_ptr newMultiRate() const { return unique_ptr(new MultiRate); diff --git a/include/cantera/kinetics/ReactionRateFactory.h b/include/cantera/kinetics/ReactionRateFactory.h index 594b5bb924..88cb00ddcd 100644 --- a/include/cantera/kinetics/ReactionRateFactory.h +++ b/include/cantera/kinetics/ReactionRateFactory.h @@ -38,19 +38,9 @@ class ReactionRateFactory * created. Since there is no need to instantiate more than one factory, * on all subsequent calls, a pointer to the existing factory is returned. */ - static ReactionRateFactory* factory() { - std::unique_lock lock(rate_mutex); - if (!s_factory) { - s_factory = new ReactionRateFactory; - } - return s_factory; - } + static ReactionRateFactory* factory(); - virtual void deleteFactory() { - std::unique_lock lock(rate_mutex); - delete s_factory; - s_factory = 0; - } + virtual void deleteFactory(); private: //! Pointer to the single instance of the factory diff --git a/include/cantera/kinetics/TwoTempPlasmaRate.h b/include/cantera/kinetics/TwoTempPlasmaRate.h index 00ac562346..4b86547242 100644 --- a/include/cantera/kinetics/TwoTempPlasmaRate.h +++ b/include/cantera/kinetics/TwoTempPlasmaRate.h @@ -75,9 +75,7 @@ class TwoTempPlasmaRate : public ArrheniusBase */ TwoTempPlasmaRate(double A, double b, double Ea=0.0, double EE=0.0); - TwoTempPlasmaRate(const AnyMap& node, const UnitStack& rate_units={}) : TwoTempPlasmaRate() { - setParameters(node, rate_units); - } + TwoTempPlasmaRate(const AnyMap& node, const UnitStack& rate_units={}); unique_ptr newMultiRate() const override { return unique_ptr( diff --git a/include/cantera/numerics/PreconditionerFactory.h b/include/cantera/numerics/PreconditionerFactory.h index b1726b057d..ae84223805 100644 --- a/include/cantera/numerics/PreconditionerFactory.h +++ b/include/cantera/numerics/PreconditionerFactory.h @@ -17,20 +17,10 @@ class PreconditionerBase; class PreconditionerFactory : public Factory { public: - static PreconditionerFactory* factory() { - std::unique_lock lock(precon_mutex); - if (!s_factory) { - s_factory = new PreconditionerFactory; - } - return s_factory; - }; + static PreconditionerFactory* factory(); //! Delete preconditioner factory - virtual void deleteFactory() { - std::unique_lock lock(precon_mutex); - delete s_factory; - s_factory = 0; - }; + virtual void deleteFactory(); private: static PreconditionerFactory* s_factory; @@ -39,10 +29,7 @@ class PreconditionerFactory : public Factory }; //! Create a Preconditioner object of the specified type -inline std::shared_ptr newPreconditioner(const std::string& precon) -{ - return std::shared_ptr(PreconditionerFactory::factory()->create(precon)); -}; +shared_ptr newPreconditioner(const string& precon); } diff --git a/include/cantera/oneD/Boundary1D.h b/include/cantera/oneD/Boundary1D.h index 33c3a8e2ec..db53867e31 100644 --- a/include/cantera/oneD/Boundary1D.h +++ b/include/cantera/oneD/Boundary1D.h @@ -106,16 +106,10 @@ class Inlet1D : public Boundary1D public: Inlet1D(); - Inlet1D(shared_ptr solution, const std::string& id="") : Inlet1D() { - m_solution = solution; - m_id = id; - } + Inlet1D(shared_ptr solution, const string& id=""); //! set spreading rate - virtual void setSpreadRate(double V0) { - m_V0 = V0; - needJacUpdate(); - } + virtual void setSpreadRate(double V0); //! spreading rate virtual double spreadRate() { @@ -237,12 +231,7 @@ class OutletRes1D : public Boundary1D public: OutletRes1D(); - OutletRes1D(shared_ptr solution, const std::string& id="") - : OutletRes1D() - { - m_solution = solution; - m_id = id; - } + OutletRes1D(shared_ptr solution, const string& id=""); virtual void showSolution(const double* x) {} diff --git a/include/cantera/oneD/StFlow.h b/include/cantera/oneD/StFlow.h index e41276ada2..9166949479 100644 --- a/include/cantera/oneD/StFlow.h +++ b/include/cantera/oneD/StFlow.h @@ -183,15 +183,7 @@ class StFlow : public Domain1D //! Return the type of flow domain being represented, either "Free Flame" or //! "Axisymmetric Stagnation". //! @see setFreeFlow setAxisymmetricFlow - virtual std::string flowType() const { - if (m_type == cFreeFlow) { - return "Free Flame"; - } else if (m_type == cAxisymmetricStagnationFlow) { - return "Axisymmetric Stagnation"; - } else { - throw CanteraError("StFlow::flowType", "Unknown value for 'm_type'"); - } - } + virtual string flowType() const; void solveEnergyEqn(size_t j=npos); @@ -250,9 +242,8 @@ class StFlow : public Domain1D return m_rho[j]; } - virtual bool fixed_mdot() { - return (domainType() != cFreeFlow); - } + virtual bool fixed_mdot(); + void setViscosityFlag(bool dovisc) { m_dovisc = dovisc; } diff --git a/include/cantera/thermo/PDSSFactory.h b/include/cantera/thermo/PDSSFactory.h index b3999cd3d3..1103db548a 100644 --- a/include/cantera/thermo/PDSSFactory.h +++ b/include/cantera/thermo/PDSSFactory.h @@ -17,20 +17,10 @@ class PDSSFactory : public Factory { public: //! Static function that creates a static instance of the factory. - static PDSSFactory* factory() { - std::unique_lock lock(thermo_mutex); - if (!s_factory) { - s_factory = new PDSSFactory; - } - return s_factory; - } + static PDSSFactory* factory(); //! delete the static instance of this factory - virtual void deleteFactory() { - std::unique_lock lock(thermo_mutex); - delete s_factory; - s_factory = 0; - } + virtual void deleteFactory(); //! Create a new thermodynamic property manager. /*! diff --git a/include/cantera/thermo/ThermoFactory.h b/include/cantera/thermo/ThermoFactory.h index d8d13c5d45..6828961687 100644 --- a/include/cantera/thermo/ThermoFactory.h +++ b/include/cantera/thermo/ThermoFactory.h @@ -34,20 +34,10 @@ class ThermoFactory : public Factory { public: //! Static function that creates a static instance of the factory. - static ThermoFactory* factory() { - std::unique_lock lock(thermo_mutex); - if (!s_factory) { - s_factory = new ThermoFactory; - } - return s_factory; - } + static ThermoFactory* factory(); //! delete the static instance of this factory - virtual void deleteFactory() { - std::unique_lock lock(thermo_mutex); - delete s_factory; - s_factory = 0; - } + virtual void deleteFactory(); //! Create a new thermodynamic property manager. /*! @@ -69,21 +59,14 @@ class ThermoFactory : public Factory }; //! @copydoc ThermoFactory::newThermoPhase -inline ThermoPhase* newThermoPhase(const std::string& model) -{ - return ThermoFactory::factory()->create(model); -} +ThermoPhase* newThermoPhase(const string& model); //! Create a new ThermoPhase instance. /*! * @param model String to look up the model against * @returns a shared pointer to a new ThermoPhase instance matching the model string. */ -inline shared_ptr newThermo(const std::string& model) -{ - ThermoPhase* tptr = ThermoFactory::factory()->create(model); - return shared_ptr (tptr); -} +shared_ptr newThermo(const string& model); //! Create a new ThermoPhase object and initialize it /*! diff --git a/include/cantera/transport/TransportFactory.h b/include/cantera/transport/TransportFactory.h index 4a9827455c..751fc1c7ed 100644 --- a/include/cantera/transport/TransportFactory.h +++ b/include/cantera/transport/TransportFactory.h @@ -42,13 +42,7 @@ class TransportFactory : public Factory * f = TransportFactory::factory(); * @endcode */ - static TransportFactory* factory() { - std::unique_lock transportLock(transport_mutex); - if (!s_factory) { - s_factory = new TransportFactory(); - } - return s_factory; - } + static TransportFactory* factory(); //! Deletes the statically allocated factory instance. virtual void deleteFactory(); @@ -104,16 +98,7 @@ Transport* newTransportMgr(const std::string& model="", ThermoPhase* thermo=0, * @returns a Transport object for the phase * @ingroup tranprops */ -inline shared_ptr newTransport(ThermoPhase* thermo, - const std::string& model = "default") { - Transport* tr; - if (model == "default") { - tr = TransportFactory::factory()->newTransport(thermo, 0); - } else { - tr = TransportFactory::factory()->newTransport(model, thermo, 0); - } - return shared_ptr (tr); -} +shared_ptr newTransport(ThermoPhase* thermo, const string& model="default"); //! Create a new transport manager instance. /*! diff --git a/include/cantera/zeroD/FlowDeviceFactory.h b/include/cantera/zeroD/FlowDeviceFactory.h index 671c82da7b..c7a01eec74 100644 --- a/include/cantera/zeroD/FlowDeviceFactory.h +++ b/include/cantera/zeroD/FlowDeviceFactory.h @@ -24,19 +24,9 @@ namespace Cantera class FlowDeviceFactory : public Factory { public: - static FlowDeviceFactory* factory() { - std::unique_lock lock(flowDevice_mutex); - if (!s_factory) { - s_factory = new FlowDeviceFactory; - } - return s_factory; - } - - virtual void deleteFactory() { - std::unique_lock lock(flowDevice_mutex); - delete s_factory; - s_factory = 0; - } + static FlowDeviceFactory* factory(); + + virtual void deleteFactory(); //! Create a new flow device by type name. /*! @@ -52,10 +42,7 @@ class FlowDeviceFactory : public Factory //! Create a FlowDevice object of the specified type //! @ingroup ZeroD -inline FlowDevice* newFlowDevice(const std::string& model) -{ - return FlowDeviceFactory::factory()->newFlowDevice(model); -} +FlowDevice* newFlowDevice(const string& model); } diff --git a/include/cantera/zeroD/ReactorFactory.h b/include/cantera/zeroD/ReactorFactory.h index 3993ac0ff9..9d2a6eb8db 100644 --- a/include/cantera/zeroD/ReactorFactory.h +++ b/include/cantera/zeroD/ReactorFactory.h @@ -24,19 +24,9 @@ namespace Cantera class ReactorFactory : public Factory { public: - static ReactorFactory* factory() { - std::unique_lock lock(reactor_mutex); - if (!s_factory) { - s_factory = new ReactorFactory; - } - return s_factory; - } - - virtual void deleteFactory() { - std::unique_lock lock(reactor_mutex); - delete s_factory; - s_factory = 0; - } + static ReactorFactory* factory(); + + virtual void deleteFactory(); //! Create a new reactor by type name. /*! @@ -52,10 +42,7 @@ class ReactorFactory : public Factory //! Create a Reactor object of the specified type //! @ingroup ZeroD -inline ReactorBase* newReactor(const std::string& model) -{ - return ReactorFactory::factory()->newReactor(model); -} +ReactorBase* newReactor(const string& model); } diff --git a/include/cantera/zeroD/WallFactory.h b/include/cantera/zeroD/WallFactory.h index d979fd2fec..d3c32c0726 100644 --- a/include/cantera/zeroD/WallFactory.h +++ b/include/cantera/zeroD/WallFactory.h @@ -24,19 +24,9 @@ namespace Cantera class WallFactory : public Factory { public: - static WallFactory* factory() { - std::unique_lock lock(wall_mutex); - if (!s_factory) { - s_factory = new WallFactory; - } - return s_factory; - } - - virtual void deleteFactory() { - std::unique_lock lock(wall_mutex); - delete s_factory; - s_factory = 0; - } + static WallFactory* factory(); + + virtual void deleteFactory(); //! Create a new wall by type name. /*! @@ -52,10 +42,7 @@ class WallFactory : public Factory //! Create a WallBase object of the specified type //! @ingroup ZeroD -inline WallBase* newWall(const std::string& model) -{ - return WallFactory::factory()->newWall(model); -} +WallBase* newWall(const string& model); } diff --git a/interfaces/cython/SConscript b/interfaces/cython/SConscript index ef34af3f78..6328c26bee 100644 --- a/interfaces/cython/SConscript +++ b/interfaces/cython/SConscript @@ -33,16 +33,24 @@ if env["coverage"]: # Build the Python module cython_obj = [] for pyxfile in multi_glob(localenv, "cantera", "pyx"): - cythonized = localenv.Command( + if pyxfile.name == "_utils.pyx": + # Define GIT_COMMIT only in _utils.pyx to avoid unnecessary recompilations + cython_env = localenv.Clone() + cython_env.Append(CPPDEFINES={'GIT_COMMIT': '\\"{0}\\"'.format(env['git_commit'])}) + else: + cython_env = localenv + + cythonized = cython_env.Command( f"cantera/{pyxfile.name.replace('.pyx', '.cpp')}", pyxfile, f'''${{python_cmd}} -c "import Cython.Build; Cython.Build.cythonize(r'${{SOURCE}}', compiler_directives={directives!r})"''' ) - for pxd in multi_glob(localenv, "cantera", "pxd"): - localenv.Depends(cythonized, pxd) + for pxd in multi_glob(cython_env, "cantera", "pxd"): + cython_env.Depends(cythonized, pxd) - obj = localenv.SharedObject( + obj = cython_env.SharedObject( f"#build/temp-py/{pyxfile.name.split('.')[0]}", cythonized) cython_obj.append(obj) +cython_obj.extend(env['python_ext_objects']) module_ext = localenv["py_module_ext"] ext = localenv.LoadableModule(f"cantera/_cantera{module_ext}", @@ -61,7 +69,29 @@ env['python_extension'] = ext localenv.Depends(mod, [ext, dataFiles, setup_cfg, readme, license, "setup.py", "pyproject.toml", "cantera/test/README.txt", "cantera/examples/README.txt"]) -localenv.Depends(ext, localenv['cantera_staticlib']) + +if env['OS'] == 'Windows': + # On Windows, the cantera library directory is likely not to be on the path. + # However, Windows does search the directory containing a library (i.e. the + # Python extension module) for DLL dependencies. + dll = [f for f in localenv['cantera_shlib'] if f.name.endswith('.dll')][0] + copy_dll = localenv.Command(f'cantera/{dll.name}', dll, Copy("$TARGET", "$SOURCE")) + localenv.Depends(ext, copy_dll) + + # If compiling with MinGW, there are some system libraries that also seem + # to need to be installed alongside the Python extension -- elsewhere on the path + # does not appear to work. + if env['toolchain'] == 'mingw': + mingw_dir = Path(which(env.subst('$CXX'))).parent + prefixes = ['libgcc', 'libstdc++', 'libwinpthread'] + for lib in mingw_dir.glob('*.dll'): + if any(lib.name.startswith(prefix) for prefix in prefixes): + copy_lib = localenv.Command(f'cantera/{lib.name}', str(lib), + Copy('$TARGET', '$SOURCE')) + localenv.Depends(mod, copy_lib) + +else: + localenv.Depends(ext, localenv['cantera_shlib']) for f in (multi_glob(localenv, 'cantera', 'py') + multi_glob(localenv, 'cantera/*', 'py')): diff --git a/interfaces/cython/cantera/_cantera.pyx b/interfaces/cython/cantera/_cantera.pyx index 4503566d6c..93ea5c8bb3 100644 --- a/interfaces/cython/cantera/_cantera.pyx +++ b/interfaces/cython/cantera/_cantera.pyx @@ -30,8 +30,8 @@ def bootstrap_cython_submodules(): bootstrap_cython_submodules() # Import the contents of the individual .pyx files -from ._onedim import * from ._utils import * +from ._onedim import * from .solutionbase import * from .delegator import * from .func1 import * diff --git a/interfaces/cython/cantera/_utils.pxd b/interfaces/cython/cantera/_utils.pxd index 453d1c4ca9..81d3e1e925 100644 --- a/interfaces/cython/cantera/_utils.pxd +++ b/interfaces/cython/cantera/_utils.pxd @@ -72,12 +72,14 @@ cdef extern from "cantera/base/global.h" namespace "Cantera": cdef void Cxx_suppress_thermo_warnings "Cantera::suppress_thermo_warnings" (cbool) cdef void Cxx_use_legacy_rate_constants "Cantera::use_legacy_rate_constants" (cbool) cdef string CxxGitCommit "Cantera::gitCommit" () + cdef string CxxVersion "Cantera::version" () cdef cbool CxxUsesHDF5 "Cantera::usesHDF5" () cdef cbool CxxDebugModeEnabled "Cantera::debugModeEnabled" () cdef extern from "cantera/cython/utils_utils.h": - cdef string get_cantera_version() + cdef string get_cantera_version_py() + cdef string get_cantera_git_commit_py() cdef int get_sundials_version() cdef cppclass CxxPythonLogger "PythonLogger": pass diff --git a/interfaces/cython/cantera/_utils.pyx b/interfaces/cython/cantera/_utils.pyx index 2ca1b29331..e1cdd25955 100644 --- a/interfaces/cython/cantera/_utils.pyx +++ b/interfaces/cython/cantera/_utils.pyx @@ -49,10 +49,20 @@ def get_data_directories(): __sundials_version__ = '.'.join(str(get_sundials_version())) -__version__ = pystr(get_cantera_version()) +__version__ = pystr(CxxVersion()) + +if __version__ != pystr(get_cantera_version_py()): + raise ImportError("Mismatch betweeen Cantera Python module version " + f"({pystr(get_cantera_version_py())}) and Cantera shared library " + f"version ({__version__})") __git_commit__ = pystr(CxxGitCommit()) +if __git_commit__ != pystr(get_cantera_git_commit_py()): + raise ImportError("Mismatch betweeen Cantera Python module Git commit " + f"({pystr(get_cantera_git_commit_py())}) and Cantera shared library " + f"git commit ({__git_commit__})") + _USE_SPARSE = False def debug_mode_enabled(): diff --git a/interfaces/cython/cantera/delegator.pxd b/interfaces/cython/cantera/delegator.pxd index 67f96a9660..f3a93fcfec 100644 --- a/interfaces/cython/cantera/delegator.pxd +++ b/interfaces/cython/cantera/delegator.pxd @@ -62,12 +62,24 @@ cdef extern from "cantera/cython/funcWrapper.h": cdef function[int(size_t&, const string&)] pyOverride( PyObject*, int(PyFuncInfo&, size_t&, const string&)) +cdef extern from "cantera/base/ExtensionManager.h" namespace "Cantera": + cdef cppclass CxxExtensionManager "Cantera::ExtensionManager": + void registerRateBuilder(string&, string&, string&) except +translate_exception + void registerRateDataBuilder(string&, string&, string&) except +translate_exception + + shared_ptr[CxxExtensionManager] build(string&) + cdef extern from "cantera/extensions/PythonExtensionManager.h" namespace "Cantera": - cdef cppclass CxxPythonExtensionManager "Cantera::PythonExtensionManager": + cdef cppclass CxxPythonExtensionManager "Cantera::PythonExtensionManager" (CxxExtensionManager): @staticmethod - void registerPythonRateBuilder(string&, string&, string&) except +translate_exception + void registerSelf() + + +cdef extern from "cantera/base/ExtensionManagerFactory.h" namespace "Cantera": + cdef cppclass CxxExtensionManagerFactory "Cantera::ExtensionManagerFactory": @staticmethod - void registerPythonRateDataBuilder(string&, string&, string&) except +translate_exception + shared_ptr[CxxExtensionManager] build(string&) + ctypedef CxxDelegator* CxxDelegatorPtr diff --git a/interfaces/cython/cantera/delegator.pyx b/interfaces/cython/cantera/delegator.pyx index 0605c9dd11..776bd141d4 100644 --- a/interfaces/cython/cantera/delegator.pyx +++ b/interfaces/cython/cantera/delegator.pyx @@ -345,6 +345,8 @@ cdef int assign_delegates(obj, CxxDelegator* delegator) except -1: _rate_delegators = [] _rate_data_delegators = [] +CxxPythonExtensionManager.registerSelf() + def extension(*, name, data=None): """ A decorator for declaring Cantera extensions that should be registered with @@ -392,11 +394,14 @@ def extension(*, name, data=None): .. versionadded:: 3.0 """ def decorator(cls): + cdef shared_ptr[CxxExtensionManager] mgr = ( + CxxExtensionManagerFactory.build(stringify("python"))) + if issubclass(cls, ExtensibleRate): cls._reaction_rate_type = name # Registering immediately supports the case where the main # application is Python - CxxPythonExtensionManager.registerPythonRateBuilder( + mgr.get().registerRateBuilder( stringify(cls.__module__), stringify(cls.__name__), stringify(name)) # Deferred registration supports the case where the main application @@ -406,7 +411,7 @@ def extension(*, name, data=None): # Register the ReactionData delegator if not issubclass(data, ExtensibleRateData): raise ValueError("'data' must inherit from 'ExtensibleRateData'") - CxxPythonExtensionManager.registerPythonRateDataBuilder( + mgr.get().registerRateDataBuilder( stringify(data.__module__), stringify(data.__name__), stringify(name)) _rate_data_delegators.append((data.__module__, data.__name__, name)) else: diff --git a/interfaces/cython/setup.cfg.in b/interfaces/cython/setup.cfg.in index cd0aef3bad..89f38f4780 100644 --- a/interfaces/cython/setup.cfg.in +++ b/interfaces/cython/setup.cfg.in @@ -51,7 +51,7 @@ packages = # The module extension needs to be here since we don't want setuptools to compile # the extension, so there are no ``source`` files in the setup.py ``extension`` and # we have to treat the module as package data. -cantera = *.pxd, *@py_module_ext@, test/*.txt, examples/*.txt, data/*.* +cantera = *.pxd, *.dll, *@py_module_ext@, test/*.txt, examples/*.txt, data/*.* [options.extras_require] hdf5 = h5py diff --git a/samples/cxx/SConscript b/samples/cxx/SConscript index 74232957e0..bd72779c4e 100644 --- a/samples/cxx/SConscript +++ b/samples/cxx/SConscript @@ -40,7 +40,7 @@ set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}) localenv.Append( LINKFLAGS=env.subst("${RPATHPREFIX}${ct_libdir}${RPATHSUFFIX}")) - localenv.Append(LIBS=env['cantera_libs']) + localenv.Append(LIBS=env['cantera_shared_libs']) localenv.Prepend(CPPPATH=['#include']) if openmp and not env['HAS_OPENMP']: diff --git a/site_scons/buildutils.py b/site_scons/buildutils.py index 88fb63500d..40fb400508 100644 --- a/site_scons/buildutils.py +++ b/site_scons/buildutils.py @@ -1139,15 +1139,15 @@ def multi_glob(env: "SCEnvironment", subdir: str, *args: str): return matches -def which(program: str) -> bool: +def which(program: str) -> "Optional[str]": """Replicates the functionality of the 'which' shell command.""" for ext in ("", ".exe", ".bat"): fpath = Path(program + ext) for path in os.environ["PATH"].split(os.pathsep): exe_file = Path(path).joinpath(fpath) if exe_file.exists() and os.access(exe_file, os.X_OK): - return True - return False + return str(exe_file) + return None def listify(value: "Union[str, Iterable]") -> "List[str]": @@ -1285,7 +1285,7 @@ def setup_python_env(env): plat = info['plat'].replace('-', '_').replace('.', '_') numpy_include = info["numpy_include"] env.Prepend(CPPPATH=[Dir('#include'), inc, numpy_include]) - env.Prepend(LIBS=env['cantera_libs']) + env.Prepend(LIBS=env['cantera_shared_libs']) # Fix the module extension for Windows from the sysconfig library. # See https://github.com/python/cpython/pull/22088 and diff --git a/src/SConscript b/src/SConscript index c963feb507..ea7534f3f0 100644 --- a/src/SConscript +++ b/src/SConscript @@ -1,5 +1,6 @@ from buildutils import * from pathlib import Path +import re Import('env', 'build', 'install', 'libraryTargets') @@ -41,6 +42,7 @@ libs = [('base', ['cpp'], baseSetup), localenv = env.Clone() localenv.Prepend(CPPPATH=[Dir('#include'), Dir('#include/cantera/ext'), Dir('.')]) localenv.Append(CCFLAGS=env['warning_flags']) +indicatorEnv = localenv.Clone() # Get this before any of the PCH complications if env['CC'] == 'cl' and env['debug']: env['use_pch'] = False # PCH doesn't work with per-file PDB @@ -83,10 +85,9 @@ if env["python_package"] == "full": ('''${python_cmd} -c "import Cython.Build; ''' f'''Cython.Build.cythonize(r'${{SOURCE}}', build_dir='{build_dir}')"''') ) - for pxd in multi_glob(localenv, "#interfaces/cython/cantera", "pxd"): + for pxd in multi_glob(pyenv, "#interfaces/cython/cantera", "pxd"): localenv.Depends(cythonized, pxd) - obj = pyenv.SharedObject(cythonized[0]) if env["OS"] == "Windows": escaped_home = '\\"' + pyenv["py_base"].replace("\\", "\\\\") + '\\"' pyenv.Append(CPPDEFINES={"CT_PYTHONHOME": escaped_home}) @@ -95,15 +96,28 @@ if env["python_package"] == "full": env.Command('#src/extensions/pythonExtensions.h', '#build/src/extensions/pythonExtensions.h', Copy('$TARGET', '$SOURCE')) - libraryTargets.append(obj) - libraryTargets.append(pyenv.SharedObject("extensions/PythonExtensionManager.cpp")) - localenv.Append(LIBS=pyenv["py_libs"], LIBPATH=pyenv["py_libpath"]) - env["cantera_libs"].extend(pyenv["py_libs"]) - env.Append(LIBPATH=pyenv["py_libpath"]) - env["extra_lib_dirs"].extend(pyenv["py_libpath"]) + + env['python_ext_objects'] = [ + pyenv.SharedObject(cythonized[0]), + pyenv.SharedObject("extensions/PythonExtensionManager.cpp") + ] + pyenv.Append(LIBS=pyenv["py_libs"], LIBPATH=pyenv["py_libpath"]) + pylibname = f"../lib/cantera_python{pyenv['py_version_short'].replace('.', '_')}" + if pyenv["versioned_shared_library"]: + lib = build(pyenv.SharedLibrary(pylibname, env['python_ext_objects'], + SPAWN=get_spawn(pyenv), + SHLIBVERSION=pyenv["cantera_pure_version"])) + install(pyenv.InstallVersionedLib, "$inst_libdir", lib) + else: + lib = build(pyenv.SharedLibrary(pylibname, env['python_ext_objects'], + SPAWN=get_spawn(pyenv))) + install("$inst_libdir", lib) + + # build the Cantera static library -lib = build(localenv.StaticLibrary('../lib/cantera', libraryTargets, +staticIndicator = indicatorEnv.SharedObject('extensions/canteraStatic.cpp') +lib = build(localenv.StaticLibrary('../lib/cantera', libraryTargets + staticIndicator, SPAWN=get_spawn(localenv))) localenv.Depends(lib, localenv['config_h_target']) install('$inst_libdir', lib) @@ -112,6 +126,45 @@ env['cantera_staticlib'] = lib localenv.Append(LIBS=localenv['external_libs'], LIBPATH=localenv['sundials_libdir'] + localenv['blas_lapack_dir']) +def create_def_file(target, source, env): + # Adapted from https://stackoverflow.com/a/58958294 + startPoint = False + # Avoid exporting some unnecessary symbols + exclusions = [ + lambda name: name.startswith(('??_G', '??_E')), # deleting destructors; generate warning LNK4102 + lambda name: name.startswith(('??_C', '__real@', '__xmm')), # various constants + lambda name: name.startswith(('??$forward', '??$addressof', '??$construct', '??$destroy')), + lambda name: name.startswith(('SUN', 'N_V', 'CV', 'cv', 'IDA', 'ida')), # Sundials + lambda name: ' +#include + #include #include #include using std::string; using std::endl; +namespace ba = boost::algorithm; #ifdef _WIN32 #include @@ -399,14 +404,59 @@ std::string Application::findInputFile(const std::string& name) void Application::loadExtension(const string& extType, const string& name) { + if (!usingSharedLibrary()) { + throw CanteraError("Application::loadExtension", + "Loading extensions requires linking to the Cantera shared library\n" + "rather than the static library"); + } if (m_loaded_extensions.count({extType, name})) { return; } - auto manager = ExtensionManagerFactory::build(extType); - manager->registerRateBuilders(name); + + if (extType == "python" && !ExtensionManagerFactory::factory().exists("python")) { + string errors; + + // type of imported symbol: void function with no arguments + typedef void (loader_t)(); + + // Only one Python module can be loaded at a time, and a handle needs to be held + // to prevent it from being unloaded. + static std::function loader; + bool loaded = false; + + for (const auto& py_ver : m_pythonSearchVersions) { + string py_ver_underscore = ba::replace_all_copy(py_ver, ".", "_"); + try { + loader = boost::dll::import_alias( + "cantera_python" + py_ver_underscore, // library name + "registerPythonExtensionManager", // symbol to import + // append extensions and prefixes, search normal library path, and + // expose all loaded symbols (specifically, those from libpython) + boost::dll::load_mode::search_system_folders + | boost::dll::load_mode::append_decorations + | boost::dll::load_mode::rtld_global + ); + loader(); + loaded = true; + break; + } catch (std::exception& err) { + errors += fmt::format("\nPython {}: {}\n", py_ver, err.what()); + } + } + if (!loaded) { + throw CanteraError("Application::loadExtension", + "Error loading Python extension support. Tried the following:{}", + errors); + } + } + ExtensionManagerFactory::build(extType)->registerRateBuilders(name); m_loaded_extensions.insert({extType, name}); } +void Application::searchPythonVersions(const string& versions) { + ba::split(m_pythonSearchVersions, versions, ba::is_any_of(",")); +} + Application* Application::s_app = 0; } // namespace Cantera diff --git a/src/base/application.h b/src/base/application.h index 883156639e..85a948a12b 100644 --- a/src/base/application.h +++ b/src/base/application.h @@ -286,13 +286,19 @@ class Application //! Load an extension implementing user-defined models //! @param extType Specifies the interface / language of the extension, for example //! "python" - //! @param extName Specifies the name of the extension. The meaning of this + //! @param name Specifies the name of the extension. The meaning of this //! parameter depends on the specific extension interface. For example, for //! Python extensions, this is the name of the Python module containing the //! models. //! @since New in Cantera 3.0 void loadExtension(const std::string& extType, const std::string& name); + //! Set the versions of Python to try when loading user-defined extensions, + //! in order of preference. Separate multiple versions with commas, for example + //! `"3.11,3.10"`. + //! @since New in Cantera 3.0 + void searchPythonVersions(const string& versions); + #ifdef _WIN32 long int readStringRegistryKey(const std::string& keyName, const std::string& valueName, std::string& value, const std::string& defaultValue); @@ -432,6 +438,9 @@ class Application //! Current vector of input directories to search for input files std::vector inputDirs; + //! Versions of Python to consider when attempting to load user extensions + vector m_pythonSearchVersions = {"3.11", "3.10", "3.9", "3.8"}; + //! Vector of deprecation warnings that have been emitted (to suppress //! duplicates) std::set warnings; diff --git a/src/base/global.cpp b/src/base/global.cpp index 563326cd77..2dbeae2800 100644 --- a/src/base/global.cpp +++ b/src/base/global.cpp @@ -122,6 +122,11 @@ void thread_complete() app()->thread_complete(); } +string version() +{ + return CANTERA_VERSION; +} + std::string gitCommit() { #ifdef GIT_COMMIT @@ -161,6 +166,10 @@ void loadExtensions(const AnyMap& node) } } +void searchPythonVersions(const string& versions) { + app()->searchPythonVersions(versions); +} + bool debugModeEnabled() { #ifdef NDEBUG diff --git a/src/clib/ct.cpp b/src/clib/ct.cpp index daf0f7591c..b4e8602297 100644 --- a/src/clib/ct.cpp +++ b/src/clib/ct.cpp @@ -11,7 +11,6 @@ // This file is part of Cantera. See License.txt in the top-level directory or // at https://cantera.org/license.txt for license and copyright information. -#define CANTERA_USE_INTERNAL #include "cantera/clib/ct.h" // Cantera includes diff --git a/src/clib/ctfunc.cpp b/src/clib/ctfunc.cpp index c64a8a7a48..d4e103cc6f 100644 --- a/src/clib/ctfunc.cpp +++ b/src/clib/ctfunc.cpp @@ -5,7 +5,6 @@ // This file is part of Cantera. See License.txt in the top-level directory or // at https://cantera.org/license.txt for license and copyright information. -#define CANTERA_USE_INTERNAL #include "cantera/clib/ctfunc.h" #include "cantera/numerics/Func1.h" diff --git a/src/clib/ctmultiphase.cpp b/src/clib/ctmultiphase.cpp index c57fc663b5..8f3af641b0 100644 --- a/src/clib/ctmultiphase.cpp +++ b/src/clib/ctmultiphase.cpp @@ -5,7 +5,6 @@ // This file is part of Cantera. See License.txt in the top-level directory or // at https://cantera.org/license.txt for license and copyright information. -#define CANTERA_USE_INTERNAL #include "cantera/clib/ctmultiphase.h" // Cantera includes diff --git a/src/clib/ctonedim.cpp b/src/clib/ctonedim.cpp index 52fa0d57f7..5918bd4962 100644 --- a/src/clib/ctonedim.cpp +++ b/src/clib/ctonedim.cpp @@ -5,8 +5,6 @@ // This file is part of Cantera. See License.txt in the top-level directory or // at https://cantera.org/license.txt for license and copyright information. -#define CANTERA_USE_INTERNAL - #include "cantera/clib/ctonedim.h" // Cantera includes diff --git a/src/clib/ctreactor.cpp b/src/clib/ctreactor.cpp index 8c46431abe..53f9a4535f 100644 --- a/src/clib/ctreactor.cpp +++ b/src/clib/ctreactor.cpp @@ -5,7 +5,6 @@ // This file is part of Cantera. See License.txt in the top-level directory or // at https://cantera.org/license.txt for license and copyright information. -#define CANTERA_USE_INTERNAL #include "cantera/clib/ctreactor.h" // Cantera includes diff --git a/src/clib/ctrpath.cpp b/src/clib/ctrpath.cpp index 1ee6724fc5..dc0740922b 100644 --- a/src/clib/ctrpath.cpp +++ b/src/clib/ctrpath.cpp @@ -5,7 +5,6 @@ // This file is part of Cantera. See License.txt in the top-level directory or // at https://cantera.org/license.txt for license and copyright information. -#define CANTERA_USE_INTERNAL #include "cantera/clib/ctrpath.h" // Cantera includes diff --git a/src/clib/ctsurf.cpp b/src/clib/ctsurf.cpp index 65c4eb21c6..3242ea1e71 100644 --- a/src/clib/ctsurf.cpp +++ b/src/clib/ctsurf.cpp @@ -6,7 +6,6 @@ // at https://cantera.org/license.txt for license and copyright information. // clib header information -#define CANTERA_USE_INTERNAL #include "cantera/clib/ctsurf.h" // Cantera includes diff --git a/src/extensions/PythonExtensionManager.cpp b/src/extensions/PythonExtensionManager.cpp index e00d78d017..ee6a8b89c1 100644 --- a/src/extensions/PythonExtensionManager.cpp +++ b/src/extensions/PythonExtensionManager.cpp @@ -5,6 +5,7 @@ #include "cantera/extensions/PythonExtensionManager.h" #include "cantera/extensions/PythonHandle.h" +#include "cantera/base/ExtensionManagerFactory.h" #include "cantera/kinetics/ReactionRateFactory.h" #include "cantera/kinetics/ReactionRateDelegator.h" @@ -19,6 +20,16 @@ #include #endif +#define BOOST_DLL_USE_STD_FS +#ifdef __MINGW32__ +#define BOOST_DLL_FORCE_ALIAS_INSTANTIATION +#endif +#include + +// This creates and exports the name that is imported from Application::loadExtension +BOOST_DLL_ALIAS(Cantera::PythonExtensionManager::registerSelf, + registerPythonExtensionManager); + namespace ba = boost::algorithm; using namespace std; @@ -154,10 +165,16 @@ PythonExtensionManager::PythonExtensionManager() s_imported = true; } +void PythonExtensionManager::registerSelf() +{ + ExtensionManagerFactory::factory().reg("python", + []() { return new PythonExtensionManager(); }); +} + void PythonExtensionManager::registerRateBuilders(const string& extensionName) { // Each rate builder class is decorated with @extension, which calls the - // registerPythonRateBuilder method to register that class. So all we have + // registerRateBuilder method to register that class. So all we have // to do here is load the module. PyObject* module_name = PyUnicode_FromString(extensionName.c_str()); PyObject* py_module = PyImport_Import(module_name); @@ -169,7 +186,7 @@ void PythonExtensionManager::registerRateBuilders(const string& extensionName) ct_registerReactionDelegators(); } -void PythonExtensionManager::registerPythonRateBuilder( +void PythonExtensionManager::registerRateBuilder( const std::string& moduleName, const std::string& className, const std::string& rateName) { @@ -198,12 +215,11 @@ void PythonExtensionManager::registerPythonRateBuilder( ReactionRateFactory::factory()->reg(rateName, builder); } -void PythonExtensionManager::registerPythonRateDataBuilder( +void PythonExtensionManager::registerRateDataBuilder( const string& moduleName, const string& className, const string& rateName) { // Make sure the helper module has been loaded PythonExtensionManager mgr; - // Create a function that links a C++ ReactionDataDelegator // object and a Python ExtensibleRateData object of a particular type, and register // this function for making that link @@ -218,7 +234,7 @@ void PythonExtensionManager::registerPythonRateDataBuilder( } delegator.setWrapper(make_shared(extData, false)); }; - ExtensionManager::registerReactionDataLinker(rateName, builder); + mgr.registerReactionDataLinker(rateName, builder); // Create a function that will link a Python Solution object to the C++ Solution // object that gets passed to the Reaction @@ -231,7 +247,7 @@ void PythonExtensionManager::registerPythonRateDataBuilder( } return make_shared(pySoln, false); }; - ExtensionManager::registerSolutionLinker("python", solnLinker); + mgr.registerSolutionLinker("python", solnLinker); } }; diff --git a/src/extensions/canteraShared.cpp b/src/extensions/canteraShared.cpp new file mode 100644 index 0000000000..daf921a32b --- /dev/null +++ b/src/extensions/canteraShared.cpp @@ -0,0 +1,18 @@ +//! @file canteraShared.cpp + +// This file is part of Cantera. See License.txt in the top-level directory or +// at https://cantera.org/license.txt for license and copyright information. + +#include "cantera/base/ct_defs.h" + +namespace Cantera +{ + +bool usingSharedLibrary() +{ + // This implementation of usingSharedLibrary is compiled and embedded + // only in the Cantera shared library + return true; +} + +} diff --git a/src/extensions/canteraStatic.cpp b/src/extensions/canteraStatic.cpp new file mode 100644 index 0000000000..0818ee5759 --- /dev/null +++ b/src/extensions/canteraStatic.cpp @@ -0,0 +1,18 @@ +//! @file canteraStatic.cpp + +// This file is part of Cantera. See License.txt in the top-level directory or +// at https://cantera.org/license.txt for license and copyright information. + +#include "cantera/base/ct_defs.h" + +namespace Cantera +{ + +bool usingSharedLibrary() +{ + // This implementation of usingSharedLibrary is compiled and embedded + // only in the Cantera static library + return false; +} + +} diff --git a/src/extensions/pythonExtensions.pyx b/src/extensions/pythonExtensions.pyx index 5a558b304e..48c67524de 100644 --- a/src/extensions/pythonExtensions.pyx +++ b/src/extensions/pythonExtensions.pyx @@ -28,13 +28,15 @@ cdef extern from "cantera/kinetics/ReactionRateDelegator.h" namespace "Cantera": CxxReactionRateDelegator() -cdef extern from "cantera/extensions/PythonExtensionManager.h" namespace "Cantera": - cdef cppclass CxxPythonExtensionManager "Cantera::PythonExtensionManager": - @staticmethod - void registerPythonRateBuilder(string&, string&, string&) - @staticmethod - void registerPythonRateDataBuilder(string&, string&, string&) +cdef extern from "cantera/base/ExtensionManager.h" namespace "Cantera": + cdef cppclass CxxExtensionManager "Cantera::ExtensionManager": + void registerRateBuilder(string&, string&, string&) + void registerRateDataBuilder(string&, string&, string&) +cdef extern from "cantera/base/ExtensionManagerFactory.h" namespace "Cantera": + cdef cppclass CxxExtensionManagerFactory "Cantera::ExtensionManagerFactory": + @staticmethod + shared_ptr[CxxExtensionManager] build(string&) cdef public char* ct_getExceptionString(object exType, object exValue, object exTraceback): import traceback @@ -67,15 +69,16 @@ cdef public object ct_newPythonExtensibleRateData(CxxReactionDataDelegator* dele cdef public ct_registerReactionDelegators(): + cdef shared_ptr[CxxExtensionManager] mgr = ( + CxxExtensionManagerFactory.build(stringify("python"))) + for module, cls, name in ct.delegator._rate_delegators: - CxxPythonExtensionManager.registerPythonRateBuilder( - stringify(module), stringify(cls), stringify(name)) + mgr.get().registerRateBuilder(stringify(module), stringify(cls), stringify(name)) ct.delegator._rate_delegators.clear() for module, cls, name in ct.delegator._rate_data_delegators: - CxxPythonExtensionManager.registerPythonRateDataBuilder( - stringify(module), stringify(cls), stringify(name)) + mgr.get().registerRateDataBuilder(stringify(module), stringify(cls), stringify(name)) ct.delegator._rate_data_delegators.clear() diff --git a/src/kinetics/Arrhenius.cpp b/src/kinetics/Arrhenius.cpp index 8eda9b77cc..1b2df13585 100644 --- a/src/kinetics/Arrhenius.cpp +++ b/src/kinetics/Arrhenius.cpp @@ -20,6 +20,17 @@ ArrheniusBase::ArrheniusBase(double A, double b, double Ea) m_valid = true; } +ArrheniusBase::ArrheniusBase(const AnyValue& rate, const UnitSystem& units, + const UnitStack& rate_units) +{ + setRateParameters(rate, units, rate_units); +} + +ArrheniusBase::ArrheniusBase(const AnyMap& node, const UnitStack& rate_units) +{ + setParameters(node, rate_units); +} + void ArrheniusBase::setRateParameters( const AnyValue& rate, const UnitSystem& units, const UnitStack& rate_units) { diff --git a/src/kinetics/BlowersMaselRate.cpp b/src/kinetics/BlowersMaselRate.cpp index 8cfa5738ac..45c7cdae6a 100644 --- a/src/kinetics/BlowersMaselRate.cpp +++ b/src/kinetics/BlowersMaselRate.cpp @@ -59,6 +59,12 @@ BlowersMaselRate::BlowersMaselRate(double A, double b, double Ea0, double w) m_E4_R = w / GasConstant; } +BlowersMaselRate::BlowersMaselRate(const AnyMap& node, const UnitStack& rate_units) + : BlowersMaselRate() +{ + setParameters(node, rate_units); +} + double BlowersMaselRate::ddTScaledFromStruct(const BlowersMaselData& shared_data) const { warn_user("BlowersMaselRate::ddTScaledFromStruct", diff --git a/src/kinetics/ChebyshevRate.cpp b/src/kinetics/ChebyshevRate.cpp index 3c2fdea8dc..71f173b6ea 100644 --- a/src/kinetics/ChebyshevRate.cpp +++ b/src/kinetics/ChebyshevRate.cpp @@ -54,6 +54,12 @@ ChebyshevRate::ChebyshevRate(double Tmin, double Tmax, double Pmin, double Pmax, setData(coeffs); } +ChebyshevRate::ChebyshevRate(const AnyMap& node, const UnitStack& rate_units) + : ChebyshevRate() +{ + setParameters(node, rate_units); +} + void ChebyshevRate::setParameters(const AnyMap& node, const UnitStack& rate_units) { ReactionRate::setParameters(node, rate_units); diff --git a/src/kinetics/Custom.cpp b/src/kinetics/Custom.cpp index 1f4abf8bc9..b79f781e6b 100644 --- a/src/kinetics/Custom.cpp +++ b/src/kinetics/Custom.cpp @@ -14,6 +14,12 @@ CustomFunc1Rate::CustomFunc1Rate() { } +CustomFunc1Rate::CustomFunc1Rate(const AnyMap& node, const UnitStack& rate_units) + : CustomFunc1Rate() +{ + setParameters(node, rate_units); +} + void CustomFunc1Rate::setRateFunction(shared_ptr f) { m_ratefunc = f; diff --git a/src/kinetics/Falloff.cpp b/src/kinetics/Falloff.cpp index a48588bf86..312d9b5389 100644 --- a/src/kinetics/Falloff.cpp +++ b/src/kinetics/Falloff.cpp @@ -82,6 +82,12 @@ void FalloffData::restore() m_perturbed = false; } +FalloffRate::FalloffRate(const AnyMap& node, const UnitStack& rate_units) + : FalloffRate() +{ + setParameters(node, rate_units); +} + void FalloffRate::init(const vector_fp& c) { warn_deprecated("FalloffRate::init", @@ -211,6 +217,36 @@ void FalloffRate::validate(const std::string& equation, const Kinetics& kin) } } +LindemannRate::LindemannRate(const AnyMap& node, const UnitStack& rate_units) + : LindemannRate() +{ + setParameters(node, rate_units); +} + +LindemannRate::LindemannRate(const ArrheniusRate& low, const ArrheniusRate& high, + const vector_fp& c) + : LindemannRate() +{ + m_lowRate = low; + m_highRate = high; + setFalloffCoeffs(c); +} + +TroeRate::TroeRate(const AnyMap& node, const UnitStack& rate_units) + : TroeRate() +{ + setParameters(node, rate_units); +} + +TroeRate::TroeRate(const ArrheniusRate& low, const ArrheniusRate& high, + const vector_fp& c) + : TroeRate() +{ + m_lowRate = low; + m_highRate = high; + setFalloffCoeffs(c); +} + void TroeRate::setFalloffCoeffs(const vector_fp& c) { if (c.size() != 3 && c.size() != 4) { @@ -337,6 +373,12 @@ void TroeRate::getParameters(AnyMap& node) const node["Troe"] = std::move(params); } +SriRate::SriRate(const AnyMap& node, const UnitStack& rate_units) + : SriRate() +{ + setParameters(node, rate_units); +} + void SriRate::setFalloffCoeffs(const vector_fp& c) { if (c.size() != 3 && c.size() != 5) { @@ -461,6 +503,12 @@ void SriRate::getParameters(AnyMap& node) const node["SRI"] = std::move(params); } +TsangRate::TsangRate(const AnyMap& node, const UnitStack& rate_units) + : TsangRate() +{ + setParameters(node, rate_units); +} + void TsangRate::setFalloffCoeffs(const vector_fp& c) { if (c.size() != 1 && c.size() != 2) { diff --git a/src/kinetics/KineticsFactory.cpp b/src/kinetics/KineticsFactory.cpp index 0ccc728c2b..0e99e3277b 100644 --- a/src/kinetics/KineticsFactory.cpp +++ b/src/kinetics/KineticsFactory.cpp @@ -35,11 +35,36 @@ KineticsFactory::KineticsFactory() { addAlias("edge", "Edge"); } +KineticsFactory* KineticsFactory::factory() { + std::unique_lock lock(kinetics_mutex); + if (!s_factory) { + s_factory = new KineticsFactory; + } + return s_factory; +} + +void KineticsFactory::deleteFactory() { + std::unique_lock lock(kinetics_mutex); + delete s_factory; + s_factory = 0; +} + Kinetics* KineticsFactory::newKinetics(const string& model) { return create(toLowerCopy(model)); } +Kinetics* newKineticsMgr(const string& model) +{ + return KineticsFactory::factory()->newKinetics(model); +} + +shared_ptr newKinetics(const string& model) +{ + shared_ptr kin(KineticsFactory::factory()->newKinetics(model)); + return kin; +} + unique_ptr newKinetics(const vector& phases, const AnyMap& phaseNode, const AnyMap& rootNode) diff --git a/src/kinetics/PlogRate.cpp b/src/kinetics/PlogRate.cpp index 629c96d8d8..c4082bae65 100644 --- a/src/kinetics/PlogRate.cpp +++ b/src/kinetics/PlogRate.cpp @@ -64,6 +64,12 @@ PlogRate::PlogRate(const std::multimap& rates) setRates(rates); } +PlogRate::PlogRate(const AnyMap& node, const UnitStack& rate_units) + : PlogRate() +{ + setParameters(node, rate_units); +} + void PlogRate::setParameters(const AnyMap& node, const UnitStack& rate_units) { ReactionRate::setParameters(node, rate_units); diff --git a/src/kinetics/ReactionRateFactory.cpp b/src/kinetics/ReactionRateFactory.cpp index 3cd354b368..7969b4e4c1 100644 --- a/src/kinetics/ReactionRateFactory.cpp +++ b/src/kinetics/ReactionRateFactory.cpp @@ -100,6 +100,20 @@ ReactionRateFactory::ReactionRateFactory() }); } +ReactionRateFactory* ReactionRateFactory::factory() { + std::unique_lock lock(rate_mutex); + if (!s_factory) { + s_factory = new ReactionRateFactory(); + } + return s_factory; +} + +void ReactionRateFactory::deleteFactory() { + std::unique_lock lock(rate_mutex); + delete s_factory; + s_factory = 0; +} + shared_ptr newReactionRate(const std::string& type) { return shared_ptr ( diff --git a/src/kinetics/TwoTempPlasmaRate.cpp b/src/kinetics/TwoTempPlasmaRate.cpp index 62890d4a43..ce3f469496 100644 --- a/src/kinetics/TwoTempPlasmaRate.cpp +++ b/src/kinetics/TwoTempPlasmaRate.cpp @@ -59,6 +59,12 @@ TwoTempPlasmaRate::TwoTempPlasmaRate(double A, double b, double Ea, double EE) m_E4_R = EE / GasConstant; } +TwoTempPlasmaRate::TwoTempPlasmaRate(const AnyMap& node, const UnitStack& rate_units) + : TwoTempPlasmaRate() +{ + setParameters(node, rate_units); +} + double TwoTempPlasmaRate::ddTScaledFromStruct(const TwoTempPlasmaData& shared_data) const { warn_user("TwoTempPlasmaRate::ddTScaledFromStruct", diff --git a/src/numerics/PreconditionerFactory.cpp b/src/numerics/PreconditionerFactory.cpp index 0ea2c21114..9c450a9ffb 100644 --- a/src/numerics/PreconditionerFactory.cpp +++ b/src/numerics/PreconditionerFactory.cpp @@ -11,6 +11,21 @@ using namespace std; namespace Cantera { +PreconditionerFactory* PreconditionerFactory::factory() { + std::unique_lock lock(precon_mutex); + if (!s_factory) { + s_factory = new PreconditionerFactory; + } + return s_factory; +}; + +//! Delete preconditioner factory +void PreconditionerFactory::deleteFactory() { + std::unique_lock lock(precon_mutex); + delete s_factory; + s_factory = 0; +}; + PreconditionerFactory* PreconditionerFactory::s_factory = 0; std::mutex PreconditionerFactory::precon_mutex; @@ -19,4 +34,9 @@ PreconditionerFactory::PreconditionerFactory() reg("Adaptive", []() { return new AdaptivePreconditioner(); }); } +shared_ptr newPreconditioner(const string& precon) +{ + return shared_ptr(PreconditionerFactory::factory()->create(precon)); +}; + } diff --git a/src/oneD/Boundary1D.cpp b/src/oneD/Boundary1D.cpp index 9093604d21..342ef25275 100644 --- a/src/oneD/Boundary1D.cpp +++ b/src/oneD/Boundary1D.cpp @@ -96,6 +96,22 @@ Inlet1D::Inlet1D() m_xstr = ""; } +Inlet1D::Inlet1D(shared_ptr solution, const string& id) + : Inlet1D() +{ + m_solution = solution; + m_id = id; +} + + +//! set spreading rate +void Inlet1D::setSpreadRate(double V0) +{ + m_V0 = V0; + needJacUpdate(); +} + + void Inlet1D::showSolution(const double* x) { writelog(" Mass Flux: {:10.4g} kg/m^2/s \n", m_mdot); @@ -336,6 +352,13 @@ OutletRes1D::OutletRes1D() m_xstr = ""; } +OutletRes1D::OutletRes1D(shared_ptr solution, const string& id) + : OutletRes1D() +{ + m_solution = solution; + m_id = id; +} + void Outlet1D::init() { _init(0); diff --git a/src/oneD/StFlow.cpp b/src/oneD/StFlow.cpp index 70661e7a79..9b6a7b1ae6 100644 --- a/src/oneD/StFlow.cpp +++ b/src/oneD/StFlow.cpp @@ -240,6 +240,10 @@ void StFlow::setGasAtMidpoint(const doublereal* x, size_t j) m_thermo->setPressure(m_press); } +bool StFlow::fixed_mdot() { + return (domainType() != cFreeFlow); +} + void StFlow::_finalize(const doublereal* x) { if (!m_do_multicomponent && m_do_soret) { @@ -783,6 +787,16 @@ void StFlow::restore(SolutionArray& arr, double* soln, int loglevel) setMeta(arr.meta(), loglevel); } +string StFlow::flowType() const { + if (m_type == cFreeFlow) { + return "Free Flame"; + } else if (m_type == cAxisymmetricStagnationFlow) { + return "Axisymmetric Stagnation"; + } else { + throw CanteraError("StFlow::flowType", "Unknown value for 'm_type'"); + } +} + void StFlow::setMeta(const AnyMap& state, int loglevel) { if (state.hasKey("energy-enabled")) { diff --git a/src/pch/system.h b/src/pch/system.h index 6c277b279e..1ac4d4c6a3 100644 --- a/src/pch/system.h +++ b/src/pch/system.h @@ -9,12 +9,12 @@ #include #include #include +#include #include #include #include +#include #include -#include "cantera/base/fmt.h" -#include "cantera/base/AnyMap.h" #endif diff --git a/src/thermo/PDSSFactory.cpp b/src/thermo/PDSSFactory.cpp index 328f13ea5a..3d8765cab6 100644 --- a/src/thermo/PDSSFactory.cpp +++ b/src/thermo/PDSSFactory.cpp @@ -37,6 +37,20 @@ PDSSFactory::PDSSFactory() reg("HKFT", []() { return new PDSS_HKFT(); }); } +PDSSFactory* PDSSFactory::factory() { + std::unique_lock lock(thermo_mutex); + if (!s_factory) { + s_factory = new PDSSFactory; + } + return s_factory; +} + +void PDSSFactory::deleteFactory() { + std::unique_lock lock(thermo_mutex); + delete s_factory; + s_factory = 0; +} + PDSS* PDSSFactory::newPDSS(const std::string& model) { return create(model); diff --git a/src/thermo/ThermoFactory.cpp b/src/thermo/ThermoFactory.cpp index fd6edb4c92..44da6a98ab 100644 --- a/src/thermo/ThermoFactory.cpp +++ b/src/thermo/ThermoFactory.cpp @@ -106,6 +106,33 @@ ThermoFactory::ThermoFactory() reg("Peng-Robinson", []() { return new PengRobinson(); }); } +ThermoFactory* ThermoFactory::factory() +{ + std::unique_lock lock(thermo_mutex); + if (!s_factory) { + s_factory = new ThermoFactory; + } + return s_factory; +} + +void ThermoFactory::deleteFactory() +{ + std::unique_lock lock(thermo_mutex); + delete s_factory; + s_factory = 0; +} + +ThermoPhase* newThermoPhase(const string& model) +{ + return ThermoFactory::factory()->create(model); +} + +shared_ptr newThermo(const string& model) +{ + shared_ptr tptr(ThermoFactory::factory()->create(model)); + return tptr; +} + ThermoPhase* ThermoFactory::newThermoPhase(const std::string& model) { return create(model); diff --git a/src/transport/TransportFactory.cpp b/src/transport/TransportFactory.cpp index 9cfeef4a2f..c2eb227f58 100644 --- a/src/transport/TransportFactory.cpp +++ b/src/transport/TransportFactory.cpp @@ -52,6 +52,14 @@ TransportFactory::TransportFactory() m_CK_mode["CK_Multi"] = m_CK_mode["multicomponent-CK"] = true; } +TransportFactory* TransportFactory::factory() { + std::unique_lock transportLock(transport_mutex); + if (!s_factory) { + s_factory = new TransportFactory(); + } + return s_factory; +} + void TransportFactory::deleteFactory() { std::unique_lock transportLock(transport_mutex); @@ -106,6 +114,17 @@ Transport* newTransportMgr(const std::string& model, ThermoPhase* thermo, int lo return f->newTransport(model, thermo, log_level); } +shared_ptr newTransport(ThermoPhase* thermo, const string& model) +{ + Transport* tr; + if (model == "default") { + tr = TransportFactory::factory()->newTransport(thermo, 0); + } else { + tr = TransportFactory::factory()->newTransport(model, thermo, 0); + } + return shared_ptr(tr); +} + Transport* newDefaultTransportMgr(ThermoPhase* thermo, int loglevel) { return TransportFactory::factory()->newTransport(thermo, loglevel); diff --git a/src/zeroD/FlowDeviceFactory.cpp b/src/zeroD/FlowDeviceFactory.cpp index 4050b873e0..8f1ddef65a 100644 --- a/src/zeroD/FlowDeviceFactory.cpp +++ b/src/zeroD/FlowDeviceFactory.cpp @@ -20,9 +20,29 @@ FlowDeviceFactory::FlowDeviceFactory() reg("Valve", []() { return new Valve(); }); } +FlowDeviceFactory* FlowDeviceFactory::factory() { + std::unique_lock lock(flowDevice_mutex); + if (!s_factory) { + s_factory = new FlowDeviceFactory; + } + return s_factory; +} + +void FlowDeviceFactory::deleteFactory() { + std::unique_lock lock(flowDevice_mutex); + delete s_factory; + s_factory = 0; +} + + FlowDevice* FlowDeviceFactory::newFlowDevice(const std::string& flowDeviceType) { return create(flowDeviceType); } +FlowDevice* newFlowDevice(const string& model) +{ + return FlowDeviceFactory::factory()->newFlowDevice(model); +} + } diff --git a/src/zeroD/ReactorFactory.cpp b/src/zeroD/ReactorFactory.cpp index a618203756..44a32ca5a9 100644 --- a/src/zeroD/ReactorFactory.cpp +++ b/src/zeroD/ReactorFactory.cpp @@ -55,9 +55,28 @@ ReactorFactory::ReactorFactory() reg("MoleReactor", []() { return new MoleReactor(); }); } +ReactorFactory* ReactorFactory::factory() { + std::unique_lock lock(reactor_mutex); + if (!s_factory) { + s_factory = new ReactorFactory; + } + return s_factory; +} + +void ReactorFactory::deleteFactory() { + std::unique_lock lock(reactor_mutex); + delete s_factory; + s_factory = 0; +} + ReactorBase* ReactorFactory::newReactor(const std::string& reactorType) { return create(reactorType); } +ReactorBase* newReactor(const string& model) +{ + return ReactorFactory::factory()->newReactor(model); +} + } diff --git a/src/zeroD/WallFactory.cpp b/src/zeroD/WallFactory.cpp index 8bb623fc73..df32fd9105 100644 --- a/src/zeroD/WallFactory.cpp +++ b/src/zeroD/WallFactory.cpp @@ -18,9 +18,28 @@ WallFactory::WallFactory() reg("Wall", []() { return new Wall(); }); } +WallFactory* WallFactory::factory() { + std::unique_lock lock(wall_mutex); + if (!s_factory) { + s_factory = new WallFactory; + } + return s_factory; +} + +void WallFactory::deleteFactory() { + std::unique_lock lock(wall_mutex); + delete s_factory; + s_factory = 0; +} + WallBase* WallFactory::newWall(const std::string& wallType) { return create(wallType); } +WallBase* newWall(const string& model) +{ + return WallFactory::factory()->newWall(model); +} + } diff --git a/test/SConscript b/test/SConscript index 6b2150d347..419c279604 100644 --- a/test/SConscript +++ b/test/SConscript @@ -10,16 +10,9 @@ from buildutils import * Import('env','build','install') localenv = env.Clone() -# Where possible, link tests against the shared libraries to minimize the sizes -# of the resulting binaries. -if localenv['OS'] == 'Linux': - cantera_libs = localenv['cantera_shared_libs'] -else: - cantera_libs = localenv['cantera_libs'] - localenv.Prepend(CPPPATH=['#include'], LIBPATH='#build/lib') -localenv.Append(LIBS=cantera_libs, +localenv.Append(LIBS=localenv['cantera_shared_libs'], CCFLAGS=env['warning_flags']) if env['googletest'] == 'submodule': @@ -44,14 +37,6 @@ localenv.PrependENVPath('PYTHONPATH', Dir('#test/python').abspath) PASSED_FILES = {} -# Add build/lib in order to find Cantera shared library -if env["OS"] == "Windows": - localenv.PrependENVPath('PATH', Dir('#build/lib').abspath) -elif env['OS'] == 'Darwin': - localenv.PrependENVPath('DYLD_LIBRARY_PATH', Dir('#build/lib').abspath) -else: - localenv.PrependENVPath('LD_LIBRARY_PATH', Dir('#build/lib').abspath) - def addTestProgram(subdir, progName, env_vars={}): """ Compile a test program and create a targets for running diff --git a/test_problems/SConscript b/test_problems/SConscript index ec21117c75..666b640d56 100644 --- a/test_problems/SConscript +++ b/test_problems/SConscript @@ -14,26 +14,10 @@ for optimize_flag in ('-O3', '-O2', '/O2'): ccflags.remove(optimize_flag) localenv['CCFLAGS'] = ccflags -# Where possible, link tests against the shared libraries to minimize the sizes -# of the resulting binaries. -if localenv['OS'] == 'Linux': - cantera_libs = localenv['cantera_shared_libs'] -else: - cantera_libs = localenv['cantera_libs'] - localenv['ENV']['CANTERA_DATA'] = (Dir('#build/data').abspath + os.pathsep + Dir('#samples/data').abspath + os.pathsep + Dir('#test/data').abspath) -# Add build/lib in order to find Cantera shared library -if env["OS"] == "Windows": - localenv.PrependENVPath('PATH', Dir('#build/lib').abspath) -elif env['OS'] == 'Darwin': - localenv.PrependENVPath('DYLD_LIBRARY_PATH', Dir('#build/lib').abspath) -else: - localenv.PrependENVPath('LD_LIBRARY_PATH', Dir('#build/lib').abspath) - - PASSED_FILES = {} @@ -70,7 +54,7 @@ class Test(object): if source_files: self.program = localenv.Program( pjoin(self.subdir, self.programName), source_files, - LIBS=self.libs or cantera_libs) + LIBS=self.libs or localenv['cantera_shared_libs']) else: if isinstance(self.programName, str): self.programName += '$PROGSUFFIX'