Repository: thermotools/thermopack (language: python)

Issue:
C++ wrapper and cmake based build system
- Implemented header-only C++ wrapper in `addon/cppThermopack`, with examples in `addon/cppExamples`. 
- Implemented CMake-based build system that works on macOS and linux (tested on containers with Github Actions).
- Added `pFUnit` as a git submodule so that we can build the unittests with Github Actions

The wrapper is used in [`KineticGas/pure_cpp`](https://github.com/thermotools/KineticGas/tree/pure_cpp).

I've removed the workflow file from this PR, and instead developing that on [my fork](https://github.com/vegardjervell/thermopack/tree/cibw) to prevent flooding this repo with failed actions during development.

The unit tests currently fail on the Github actions Linux container with
```
Failure
 in: 
test_critical_suite.test_critical_point_mix
  Location: 
[test_critical.pf:76]
AssertEqual failure:
      Expected: <8608562.1708822846>
        Actual: <8608563.1845326163>
    Difference: <1.0136503316462040> (greater than tolerance of 1.0000000000000000)

 FAILURES!!!
Tests run: 130, Failures: 1, Errors: 0
, Disabled: 0
```
but pass on macOS with arm64 and x86.


Discussion / hints:

Fix patch:
diff --git a/.github/prerelease_bodyFile.md b/.github/prerelease_bodyFile.md
new file mode 100644
index 00000000..6a8b485e
--- /dev/null
+++ b/.github/prerelease_bodyFile.md
@@ -0,0 +1,9 @@
+This release is automatically updated whenever a pull request is merged to `main`.
+
+For stable releases of `ThermoPack`, see releases named `vX.Y.Z`.
+
+To install this release, download and unzip the appropriate zip file, then install with `pip install thermopack -f wheel-<version>-<system>/`, where `wheel-<version>-<system>` is the directory created by unzipping the file.
+
+For macOS, the wheels marked `macOS-12` are built for `x86_64` machines (using intel chips), while `macOS-latest` wheels are built for `arm64` machines (Apple Silicon, i.e. M1, M2, etc.).
+
+Documentation and user guides for the latest version are found at [thermotools.github.io/thermopack](thermotools.github.io/thermopack), but are likely not as well maintained as the documentation and guides for stable releases.
\ No newline at end of file
diff --git a/.github/scripts/before_windows_build.ps1 b/.github/scripts/before_windows_build.ps1
new file mode 100644
index 00000000..884c6a32
--- /dev/null
+++ b/.github/scripts/before_windows_build.ps1
@@ -0,0 +1,17 @@
+#Set-PSDebug -Trace 1
+$tp_version = $args[0]
+
+echo "Running before_build on windows for version $tp_version"
+mkdir build
+cd build
+cmake .. -G Ninja -DCMAKE_Fortran_COMPILER=ifort -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=Release
+echo "cmake finished ..."
+cmake --build . --config=Release --target install
+echo "build finished ..."
+# python -c "import sys; sys.path.insert(0, '../addon/pycThermopack'); import makescript; makescript.windows_make('v3')"
+python ../addon/pycThermopack/map_platform_specifics.py --diffs=$tp_version --ifort=True
+echo "--- pycThermopack contents ---"
+dir ../addon/pycThermopack
+echo "--- pycThermopack/thermopack contents ---"
+dir ../addon/pycThermopack/thermopack
+#Set-PSDebug -Off
\ No newline at end of file
diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml
new file mode 100644
index 00000000..fb4689f9
--- /dev/null
+++ b/.github/workflows/cibuildwheel.yml
@@ -0,0 +1,136 @@
+name: cibuildwheel
+
+on:
+  push:
+      branches:
+          - main
+  pull_request:
+
+jobs:
+  build_wheels:
+    if: github.ref == 'refs/heads/main' || github.event_name != 'push'
+    name: Build ${{ matrix.tp_version }} wheels on ${{ matrix.os }}-${{ matrix.version }}
+    runs-on: ${{ matrix.os }}-${{ matrix.version }}
+    timeout-minutes: 30
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [macOS, ubuntu, windows]
+        version: [latest]
+        tp_version: [v2, v3]
+        include:
+            - version: 12
+              os: macOS
+              tp_version: v2
+            - version: 12
+              os: macOS
+              tp_version: v3
+
+    steps:
+      - uses: actions/checkout@v4
+      - name: checkout submodules
+        run: git submodule update --init --recursive
+      
+      - name: Setup Linux env
+        if: matrix.os == 'ubuntu'
+        run: |
+          echo "gfortran=gfortran" >> $GITHUB_ENV
+          echo "libinspector=ls ../addon/pycThermopack/thermopack/libthermopack* | xargs ldd" >> $GITHUB_ENV
+    
+      - name: Setup macOS env
+        if: matrix.os == 'macOS'
+        run: |
+          echo "gfortran=$(which gfortran-13)" >> $GITHUB_ENV
+          echo "libinspector=otool -L ../addon/pycThermopack/thermopack/libthermopack*" >> $GITHUB_ENV
+          echo "MACOSX_DEPLOYMENT_TARGET=$(sw_vers -productVersion | cut -d '.' -f 1)" >> $GITHUB_ENV
+      
+      - name: Setup Windows env
+        if: matrix.os == 'windows'
+        uses: fortran-lang/setup-fortran@v1
+        with:
+          compiler: intel-classic
+          version: '2021.12'
+
+      - name: Check macOS deployment target
+        if: matrix.os == 'macOS'
+        run: echo "Deployment target version is ${{ env.MACOSX_DEPLOYMENT_TARGET }} / ${MACOSX_DEPLOYMENT_TARGET}"
+
+      - name: Build wheel
+        uses: pypa/cibuildwheel@v2.19.2 # run: python -m cibuildwheel --output-dir wheelhouse
+        env:
+          CIBW_ARCHS: auto64
+          CIBW_BEFORE_ALL_LINUX: |
+            yum install -y blas blas-devel
+            yum install -y lapack lapack-devel
+
+          CIBW_BEFORE_BUILD_WINDOWS: powershell -ExecutionPolicy Bypass -File .github/scripts/before_windows_build.ps1 ${{ matrix.tp_version }}
+
+          CIBW_BEFORE_BUILD: |
+            ${{ env.gfortran }} --version
+            export FC=${{ env.gfortran }}
+            mkdir build
+            cd build
+            cmake ..
+            make -j4 install
+            python ../addon/pycThermopack/map_platform_specifics.py --diffs=${{ matrix.tp_version }}
+            echo "--- pycThermopack contents ---"
+            ls ../addon/pycThermopack
+            echo "--- pycThermopack/thermopack contents ---"
+            ls ../addon/pycThermopack/thermopack
+            echo "--- Inspecting libthermopack ---"
+            ${{ env.libinspector }}
+
+          CIBW_BUILD: "*cp311*"
+          CIBW_SKIP: "*musllinux*"
+          CIBW_TEST_SKIP: "*x86_64* thermopack-2*"
+          CIBW_BUILD_VERBOSITY: 1
+        with:
+          package-dir: "./addon/pycThermopack"
+          output-dir: "./wheelhouse"
+          config-file: "{package}/pyproject.toml"
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: wheel-${{ matrix.tp_version }}-${{ matrix.os }}-${{ matrix.version }}
+          path: ./wheelhouse/*.whl
+
+  release_wheels: # Create or update the release tagged Latest-beta, and upload wheels to that release.
+    name: Update prerelease
+    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
+    needs: build_wheels
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Download created wheels
+      uses: actions/download-artifact@v4
+      with:
+        pattern: wheel-*
+    
+    - name: Display full directory status
+      run: ls -R .
+    
+    - name: zip wheels
+      run: |
+        for dir in wheel-*; do
+        zip -r "${dir}.zip" ${dir}
+        done
+    
+    - name: Display current directory status
+      run: ls
+    
+    - uses: ncipollo/release-action@v1
+      with:
+        allowUpdates: true
+        prerelease: true
+        updateOnlyUnreleased: true
+        removeArtifacts: true
+        name: "Latest (beta)"
+        tag: Latest-beta
+        commit: main
+        makeLatest: true
+        bodyFile: ".github/prerelease_bodyFile.md"
+        artifacts: "wheel-*.zip"
diff --git a/.gitignore b/.gitignore
index ae4e19d5..d7e3179f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@ syntax: glob
 
 objects
 bin
+installed/
 build/
 run*
 doc/dox.out
@@ -54,4 +55,5 @@ venv/
 venv*
 *.idea
 *.vscode
-scripts/
\ No newline at end of file
+scripts/
+!.github/scripts/
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 86784bf3..15ec21ab 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,10 +1,10 @@
+cmake_minimum_required(VERSION 3.19)
+project(ThermoPack LANGUAGES Fortran)
+
 string(ASCII 27 Esc)
 set(ColorRed "${Esc}[31m")
 set(ColorBlue "${Esc}[34m")
 
-cmake_minimum_required(VERSION 3.5)
-project(ThermoPack LANGUAGES Fortran)
-
 if(NOT MSVC)
     execute_process(COMMAND bash -c "arch" OUTPUT_VARIABLE PROC)
     set(tp_flags_common "-cpp -fPIC -fdefault-real-8 -fdefault-double-8 -frecursive -fopenmp -Wno-unused-function -Wno-unused-variable")
@@ -29,7 +29,7 @@ if(NOT MSVC)
     endif()
 else()
     # Build using: cmake --build . --config Release
-    set(tp_flags_common "/nologo /fpp /fpe:0 /names:lowercase /assume:underscore /fp:precise /extend-source:132 /iface:cref")
+    set(tp_flags_common "/nologo /fpp /fpe:0 /names:lowercase /assume:underscore /fp:precise /extend-source:132 /iface:cref /Qdiag-disable:10448")
     set(tp_optim_flags "${tp_flags_common}")
     set(tp_debug_flags "${tp_flags_common} /check:bounds /traceback")
 endif()
@@ -39,24 +39,6 @@ if(NOT CMAKE_BUILD_TYPE)
   set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
 endif()
 
-# Set FORTRAN compile flags for thermopack and lapack
-set(CMAKE_Fortran_FLAGS_RELEASE "${CMAKE_Fortran_FLAGS_RELEASE} ${tp_optim_flags}")
-set(CMAKE_Fortran_FLAGS_DEBUG "${CMAKE_Fortran_FLAGS_DEBUG} ${tp_debug_flags}")
-
-if(MSVC)
-    if(POLICY CMP0077)
-        set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
-    endif()
-    # Disable single/complex/complex16 as it is not used by thermopack
-    set(BUILD_SINGLE OFF)
-    set(BUILD_COMPLEX OFF)
-    set(BUILD_COMPLEX16 OFF)
-    add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/lapack)
-    # Add flags not comatible with lapack
-    set(CMAKE_Fortran_FLAGS_RELEASE "${CMAKE_Fortran_FLAGS_RELEASE} /real-size:64 ")
-    set(CMAKE_Fortran_FLAGS_DEBUG "${CMAKE_Fortran_FLAGS_DEBUG} /real-size:64 /warn:all,noexternal /check:all,noarg_temp_created,nopointers")
-endif(MSVC)
-
 add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src "thermopack")
 
 option(test "Build test suite" OFF)
diff --git a/MSVStudio/thermopack.def b/MSVStudio/thermopack.def
index a82e601b..4363d24f 100644
--- a/MSVStudio/thermopack.def
+++ b/MSVStudio/thermopack.def
@@ -229,4 +229,4 @@ EXPORTS
 	thermopack_var_mp_get_eos_identification_
 	tp_solver_mp_twophasetpflash_
 	sv_solver_mp_twophasesvflash_
-	uv_solver_mp_twophaseuvflash_
+	uv_solver_mp_twophaseuvflash_
\ No newline at end of file
diff --git a/addon/pycThermopack/.gitignore b/addon/pycThermopack/.gitignore
index e19f946a..f67aaaff 100644
--- a/addon/pycThermopack/.gitignore
+++ b/addon/pycThermopack/.gitignore
@@ -3,4 +3,5 @@ LICENSE*
 build/
 thermopack/platform_specifics.py
 pyctp/platform_specifics.py
-setup.py
\ No newline at end of file
+setup.py
+!scripts/
\ No newline at end of file
diff --git a/addon/pycThermopack/CMakeLists.txt b/addon/pycThermopack/CMakeLists.txt
index e69de29b..2bb6f5c3 100644
--- a/addon/pycThermopack/CMakeLists.txt
+++ b/addon/pycThermopack/CMakeLists.txt
@@ -0,0 +1,7 @@
+cmake_minimum_required(VERSION 3.16)
+project(pycThermopack)
+
+option(RECOMPILE_THERMOPACK "Recompile thermopack?" OFF)
+if(RECOMPILE_THERMOPACK)
+    add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../.. "thermopack")
+endif()
\ No newline at end of file
diff --git a/addon/pycThermopack/install b/addon/pycThermopack/install
deleted file mode 100755
index b12cae95..00000000
--- a/addon/pycThermopack/install
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env python
-# Support for python2
-from __future__ import print_function
-import os
-import sys
-import shutil
-
-
-# Setting some variables
-IN_VIRTUALENV = hasattr(sys, 'real_prefix')
-IS_ROOT = (os.getuid() == 0)
-ROOT_DIR = os.getcwd()
-
-if IS_ROOT or IN_VIRTUALENV:
-  os.system('python setup.py install')
-
-  # Cleaning up after installation.
-  shutil.rmtree(os.path.join(ROOT_DIR,'dist'), ignore_errors = True)
-  shutil.rmtree(os.path.join(ROOT_DIR,'build'), ignore_errors = True)
-  manifest_path = os.path.join(ROOT_DIR,'MANIFEST')
-  if os.path.isfile(manifest_path):
-    os.remove(manifest_path)
-
-else:
-  print("Please run as root or in a virtualenv")
-
-
diff --git a/addon/pycThermopack/makescript.py b/addon/pycThermopack/makescript.py
index c3de07b0..1ac2697a 100755
--- a/addon/pycThermopack/makescript.py
+++ b/addon/pycThermopack/makescript.py
@@ -7,35 +7,23 @@
 import sys
 import warnings
 from pathlib import Path
+from map_platform_specifics import VERSION_2, VERSION_3, pf_specifics_path
 import map_platform_specifics
 
-VERSION_2 = '2.2.3'
-VERSION_3 = '3.b0'
-
 def windows_make(diffs):
-    if diffs == 'v2':
-        version = VERSION_2
-    else:
-        version = VERSION_3
+    if diffs != 'v2':
         diffs = 'v3'
-    pf_specifics = {}
-    pf_specifics["os_id"] = "win"
-    pf_specifics["prefix"] = ""
-    pf_specifics["module"] = "_mp_"
-    pf_specifics["postfix"] = "_"
-    pf_specifics["postfix_no_module"] = "_"
-    pf_specifics["dyn_lib"] = "thermopack.dll"
+
+    pf_specifics = map_platform_specifics.get_platform_specifics_windows_ifort_whl()
     pf_specifics["diff_return_mode"] = diffs
-    pf_specifics_path = os.path.join(os.path.dirname(__file__), "thermopack", "platform_specifics.py")
     map_platform_specifics.write_platform_specifics_file(pf_specifics, pf_specifics_path)
-    map_platform_specifics.write_setup_file(f'v{version}')
-    map_platform_specifics.write_toml_file(version)
+    map_platform_specifics.set_toml_version(diffs)
 
 
 if __name__ == "__main__":
     parser = argparse.ArgumentParser()
     parser.add_argument("mode", type=str, help="optim or debug")
-    parser.add_argument("-diffs", default="v3", type=str, help="Old (v2) or new (> v2) return mode for differentials", )
+    parser.add_argument("-diffs", default="v3", type=str, help="Old (v2) or new (> v2) return mode for differentials")
 
     args = parser.parse_args()
     mode = args.mode
@@ -53,8 +41,6 @@ def windows_make(diffs):
 
     shutil.copy2(libthermo, "./thermopack/libthermopack"+libthermo.suffix)
 
-    pf_specifics_path = os.path.join(os.path.dirname(
-        __file__), "thermopack", "platform_specifics.py")
     pf_specifics = map_platform_specifics.get_platform_specifics_by_trial_and_error()
 
     if args.diffs == 'v2':
@@ -62,28 +48,15 @@ def windows_make(diffs):
     elif args.diffs == 'v3':
         version = VERSION_3
     else:
-        warnings.warn(f'-diffs={args.diffs} is not a valid value. Valid values are -diffs=[v2.1/v3], treating as -diffs=v3',
+        warnings.warn(f'-diffs={args.diffs} is not a valid value. Valid values are -diffs=[v2/v3], treating as -diffs=v3',
                       Warning)
         version = VERSION_3
         args.diffs = 'v3'
 
-    if args.diffs == 'v2':
-        warnings.warn('\033[93mYou are building ThermoPack to use the deprecated return pattern using tuples.\n'
-                      'Future versions of ThermoPack will return differentials using the `Differential` struct found in utils.py. '
-                      'To build ThermoPack to use the new return pattern, run \n`python makescript.py [optim/debug]`\n'
-                      'For more information see PR#102 at https://github.com/thermotools/thermopack/pull/102\033[0m', DeprecationWarning)
-    else:
-        warnings.warn('\033[93mYou are building ThermoPack using the "new" return pattern (i.e. the Differential structs found '
-                      "in utils.py.) \nTHIS IS THE RECOMMENDED BUILD but I'm warning you because it is not backwards compatible.\n"
-                      "The old return pattern will probably be discontinued in the future. To build "
-                      'ThermoPack with the "old" return pattern (using tuples) run \n`python makescript.py [optim/debug] -diffs=v2`\n\n'
-                      'For information on how to adapt old code to the new return pattern, see '
-                      'PR#102 at https://github.com/thermotools/thermopack/pull/102\033[0m', Warning)
+    map_platform_specifics.warn_diff_version(args.diffs)
 
     pf_specifics['diff_return_mode'] = args.diffs
     map_platform_specifics.write_platform_specifics_file(pf_specifics, pf_specifics_path)
+    map_platform_specifics.set_toml_version(version)
 
-    map_platform_specifics.write_setup_file(f'v{version}')
-    map_platform_specifics.write_toml_file(version)
-
-    print(f'\033[92mSuccessfully configured ThermoPack {version}\033[0m')
\ No newline at end of file
+    print(f'\033[92mSuccessfully configured ThermoPack {version}\033[0m')
diff --git a/addon/pycThermopack/map_platform_specifics.py b/addon/pycThermopack/map_platform_specifics.py
index cbd4f13f..74b50808 100644
--- a/addon/pycThermopack/map_platform_specifics.py
+++ b/addon/pycThermopack/map_platform_specifics.py
@@ -7,6 +7,8 @@
 import sysconfig
 from datetime import datetime
 import os
+import argparse
+import warnings
 
 # GNU FORTRAN
 G_PREFIX = "__"
@@ -19,6 +21,17 @@
 I_POSTFIX = "_"
 I_POSTFIX_NM = "_"
 
+VERSION_2 = '2.2.4b0'
+VERSION_3 = '3.b0'
+pf_specifics_path = os.path.join(os.path.dirname( __file__), "thermopack", "platform_specifics.py")
+
+class bcolor:
+    green = '\033[92m'
+    blue = '\033[34m'
+    yellow = '\033[93m'
+    red = '\033[31m'
+    default = '\033[0m'
+    
 
 def get_platform_specifics_from_platform():
     """Get platform specific stuff."""
@@ -74,19 +87,24 @@ def get_platform_specifics_by_trial_and_error():
     platform_specifics["dyn_lib"] = ""
 
     dynlibs = ["libthermopack.so", "thermopack.dll", "libthermopack.dylib"]
+    thermopack_dir = path.join(path.dirname(__file__), "thermopack")
+    errors = []
     for lib in dynlibs:
-        dyn_lib_path = path.join(path.dirname(__file__), "thermopack", lib)
+        dyn_lib_path = path.join(thermopack_dir, lib)
         try:
             tp = cdll.LoadLibrary(dyn_lib_path)
-        except OSError:
-            tp = None
-        #print(dyn_lib_path, tp)
-        if tp is not None:
             platform_specifics["dyn_lib"] = lib
             break
+        except OSError as err:
+            errors.append(err)
+            pass
     else:
-        raise FileNotFoundError(f'Could not locate ThermoPack binary! Tried '
-                                f'{[path.join(path.dirname(__file__), "thermopack", lib) for lib in dynlibs]}')
+        thermopack_contains = os.listdir(thermopack_dir)
+        load_errors = '\n\t'.join([str(err) for err in errors])
+        raise FileNotFoundError(f'Could not load ThermoPack binary!\n'
+                                f'Tried : {[path.join(thermopack_dir, lib) for lib in dynlibs]}.\n'
+                                f'{thermopack_dir} contains files : {thermopack_contains}\n'
+                                f'Errors when attempting to load :\n\t{load_errors}')
     prefixes = ["__", ""]
     moduletxt = ["_", "_MOD_", "_mp_"]
     postfixes = ["", "_"]
@@ -159,74 +177,17 @@ def write_platform_specifics_file(pf_specifics, filename):
             f.write(line)
             f.write("\n")
 
-def write_setup_file(version):
-    setup_contents = {'name': "'thermopack'",
-                      'version': f"'{version}'",
-                      'description': "'Python interface to thermopack'",
-                      'long_description': "'readme'",
-                      'long_description_content_type': "'text/markdown'",
-                      'author': "'Morten Hammer'",
-                      'author_email': "'morten.hammer@sintef.no'",
-                      'url': "'https://github.com/thermotools/thermopack'",
-                      'packages': "['thermopack']",
-                      'package_data': "{'thermopack':['*thermopack.*']}",
-                      'install_requires' : "['numpy~=1.0']"}
-
-    with open(os.path.dirname(__file__) + '/setup.py', 'w') as file:
-        file.write(f"# This file was automatically generated using the function 'write_setup_file' in \n"
-                   f"# {__file__} \n"
-                   f"# Likely called from {os.path.dirname(__file__)}/makescript.py\n"
-                   f"# Timestamp : {datetime.today().isoformat()}\n\n")
-        file.write("from distutils.core import setup\n"
-                    "from pathlib import Path\n\n"
-                    "root_dir = Path(__file__).parent # thermopack root directory\n"
-                    "readme = (root_dir / 'README_pypi.md').read_text()\n\n")
-        file.write('setup(')
-
-        for i, (k, v) in enumerate(setup_contents.items()):
-            if i > 0:
-                file.write(',')
-            file.write(f"{k}={v}\n\t")
-
-        file.write(')\n')
-
-def write_toml_file(version):
-    contents = """[build-system]
-requires = ["setuptools>=39.0"]
-build-backend = "setuptools.build_meta"
-
-[project]
-name = "thermopack"
-version = \"""" + version + """\"
-authors = [
-  { name = "Morten Hammer", email="morten.hammer@ntnu.no" },
-]
-maintainers = [
-  { name = "Morten Hammer", email="morten.hammer@ntnu.no" },
-  { name = "Vegard Gjeldvik Jervell", email="vegard.g.jervell@ntnu.no" },
-]
-description = "Python interface to thermopack"
-readme = "README_pypi.md"
-requires-python = ">=3.6"
-classifiers = [
-    "Programming Language :: Python :: 3",
-    "Programming Language :: Fortran",
-    "Operating System :: MacOS",
-    "Operating System :: POSIX :: Linux",
-    "Operating System :: Microsoft :: Windows",
-    "License :: OSI Approved :: MIT License",
-]
-keywords = ["physics", "thermodynamics", "equations_of_state", "phase_equilibria", "SAFT"]
-
-[project.urls]
-"Homepage" = "https://github.com/thermotools/thermopack"
-"Bug Tracker" = "https://github.com/thermotools/thermopack/issues"
-
-[dependencies]
-numpy = "^1.1"
-"""
-    with open(f'{os.path.dirname(__file__)}/pyproject.toml', 'w') as file:
-        file.write(contents)
+def set_toml_version(version):
+    contents = ''
+    with open(f'{os.path.dirname(__file__)}/pyproject.toml', 'r') as ifile:
+        line = ifile.readline()
+        while line:
+            if line == 'version = "0.0.0"\n':
+                line = f'version = "{version}"\n'
+            contents += line
+            line = ifile.readline()
+    with open(f'{os.path.dirname(__file__)}/pyproject.toml', 'w') as ofile:
+        ofile.write(contents)
 
 def get_platform_specifics_windows_ifort_whl():
     pf_specifics = dict()
@@ -235,12 +196,38 @@ def get_platform_specifics_windows_ifort_whl():
     pf_specifics["module"] = "_mp_"
     pf_specifics["postfix"] = "_"
     pf_specifics["postfix_no_module"] = "_"
-    pf_specifics["dyn_lib"] = "thermopack.dll"
-    pf_specifics["diff_return_mode"] = "v2"
+    pf_specifics["dyn_lib"] = "libthermopack.dll"
     return pf_specifics
-	
+
+def warn_diff_version(diffs):
+    if diffs == 'v2':
+        warnings.warn(f'{bcolor.yellow}\nYou are building ThermoPack to use the deprecated return pattern using tuples.\n'
+                      'Future versions of ThermoPack will return differentials using the `Differential` struct found in utils.py. '
+                      'To build ThermoPack to use the new return pattern, run \n\n\t`python map_platform_specifics.py [--diffs=v3 --ifort=False]`\n'
+                      'For more information see PR#102 at https://github.com/thermotools/thermopack/pull/102\n\n'
+                      f'For more information on configuration options run \n\n\t`python map_platform_specifics.py --help\n\n{bcolor.default}', Warning)
+    else:
+        print(f'{bcolor.blue}You are building ThermoPack using the "new" return pattern (i.e. the Differential structs found '
+                "in utils.py.) \nTHIS IS THE RECOMMENDED BUILD but I'm warning you because it is not backwards compatible.\n"
+                "The old return pattern will probably be discontinued in the future. To build "
+                'ThermoPack with the "old" return pattern (using tuples) run \n\n\t`python map_platform_specifics.py --diffs=v2 [--ifort=False]`\n\n'
+                'For information on how to adapt old code to the new return pattern, see '
+                'PR#102 at https://github.com/thermotools/thermopack/pull/102\n\n'
+                f'For more information on configuration options run \n\n\t`python map_platform_specifics.py --help\n\n{bcolor.default}')
+
 if __name__ == "__main__":
-    pf_specifics_path = os.path.join(os.path.dirname(
-        __file__), "thermopack", "platform_specifics.py")
-    pf_specifics_ = get_platform_specifics_by_trial_and_error()
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--diffs', default='v3', help="Old (v2) or new (v3) return mode for differentials (Default: v3)")
+    parser.add_argument('--ifort', default=False, help='Set to True if thermopack has been compiled with intel-fortran (Default: False)')
+    args = parser.parse_args()
+
+    pf_specifics_ = get_platform_specifics_by_trial_and_error() if (args.ifort is False) else get_platform_specifics_windows_ifort_whl()
+    pf_specifics_['diff_return_mode'] = args.diffs
+    pf_specifics_['version'] = VERSION_2 if (args.diffs == 'v2') else VERSION_3
+
+    warn_diff_version(args.diffs)
+
     write_platform_specifics_file(pf_specifics_, pf_specifics_path)
+    set_toml_version(pf_specifics_['version'])
+
+    print(f'{bcolor.green}Successfully configured ThermoPack {pf_specifics_["version"]}{bcolor.default}')
diff --git a/addon/pycThermopack/pyproject.toml b/addon/pycThermopack/pyproject.toml
index 3a2fa4da..3a12935b 100644
--- a/addon/pycThermopack/pyproject.toml
+++ b/addon/pycThermopack/pyproject.toml
@@ -5,10 +5,14 @@ build-backend = "scikit_build_core.build"
 [tool.scikit-build]
 sdist.include = ["libthermopack*", "platform_specifics.py"]
 wheel.expand-macos-universal-tags = true
+wheel.py-api = "py3"
+
+[tool.scikit-build.cmake.define]
+RECOMPILE_THERMOPACK = {env="RECOMPILE_THERMOPACK", default="OFF"}
 
 [project]
 name = "thermopack"
-version = "2.3b0"
+version = "0.0.0"
 authors = [
   { name = "Morten Hammer", email="morten.hammer@ntnu.no" },
 ]
@@ -35,6 +39,6 @@ dependencies = ["numpy~=1.20"]
 test = ["pytest"]
 
 [tool.cibuildwheel]
-test-command = "pytest {project}/../pyTests"
+test-command = "pytest {package}/../pyTests"
 test-requires = ["pytest"]
 build-verbosity = 2
\ No newline at end of file
diff --git a/addon/pycThermopack/build_whl_linux_x86.sh b/addon/pycThermopack/scripts/build_whl_linux_x86.sh
similarity index 100%
rename from addon/pycThermopack/build_whl_linux_x86.sh
rename to addon/pycThermopack/scripts/build_whl_linux_x86.sh
diff --git a/addon/pycThermopack/build_whl_macOS_arm64.sh b/addon/pycThermopack/scripts/build_whl_macOS_arm64.sh
similarity index 100%
rename from addon/pycThermopack/build_whl_macOS_arm64.sh
rename to addon/pycThermopack/scripts/build_whl_macOS_arm64.sh
diff --git a/addon/pycThermopack/build_whl_macOS_x86.sh b/addon/pycThermopack/scripts/build_whl_macOS_x86.sh
similarity index 100%
rename from addon/pycThermopack/build_whl_macOS_x86.sh
rename to addon/pycThermopack/scripts/build_whl_macOS_x86.sh
diff --git a/addon/pycThermopack/build_whl_windows_amd64.bat b/addon/pycThermopack/scripts/build_whl_windows_amd64.bat
similarity index 100%
rename from addon/pycThermopack/build_whl_windows_amd64.bat
rename to addon/pycThermopack/scripts/build_whl_windows_amd64.bat
diff --git a/addon/pycThermopack/build_whl_windows_win32.bat b/addon/pycThermopack/scripts/build_whl_windows_win32.bat
similarity index 100%
rename from addon/pycThermopack/build_whl_windows_win32.bat
rename to addon/pycThermopack/scripts/build_whl_windows_win32.bat
diff --git a/addon/pycThermopack/debug_compile.sh b/addon/pycThermopack/scripts/debug_compile.sh
similarity index 100%
rename from addon/pycThermopack/debug_compile.sh
rename to addon/pycThermopack/scripts/debug_compile.sh
diff --git a/addon/pycThermopack/setup.cfg b/addon/pycThermopack/setup.cfg
deleted file mode 100644
index 7cb85c34..00000000
--- a/addon/pycThermopack/setup.cfg
+++ /dev/null
@@ -1,13 +0,0 @@
-
-[options]
-packages = thermopack
-python_requires = >=3.6
-install_requires =
-    numpy ~= 1.0
-
-[options.package_data]
-* = *.py, *thermopack.*
-
-[metadata]
-license_files = 
-	LICENSE*
diff --git a/addon/pycThermopack/thermopack/__init__.py b/addon/pycThermopack/thermopack/__init__.py
index afeb489d..e69de29b 100644
--- a/addon/pycThermopack/thermopack/__init__.py
+++ b/addon/pycThermopack/thermopack/__init__.py
@@ -1,40 +0,0 @@
-"""Initialization code for the thermopack package"""
-from . import platform_specifics
-from . import thermo
-from . import cubic
-from . import tcPR
-from . import quantum_cubic
-from . import cpa
-from . import extended_csp
-from . import saft
-from . import pcsaft
-from . import saftvrmie
-from . import saftvrqmie
-from . import plotutils
-from . import utils
-from . import multiparameter
-from . import pets
-from . import thermopack_state
-from . import lee_kesler
-from . import ljs_bh
-from . import ljs_wca
-
-__all__ = ["platform_specifics",
-           "thermo",
-           "cubic",
-           "tcPR",
-           "quantum_cubic",
-           "cpa",
-           "saft",
-           "pcsaft",
-           "extended_csp",
-           "saftvrmie",
-           "saftvrqmie",
-           "plotutils",
-           "utils",
-           "multiparameter"
-           "pets",
-           "lee_kesler",
-           "thermopack_state",
-           "ljs_bh",
-           "ljs_wca"]
diff --git a/docs/vCurrent/getting_started_cpp.md b/docs/vCurrent/getting_started_cpp.md
index afab70d1..e44a90b1 100644
--- a/docs/vCurrent/getting_started_cpp.md
+++ b/docs/vCurrent/getting_started_cpp.md
@@ -36,17 +36,45 @@ The header files you will need are found in `thermopack/addon/cppThermoPack/cppT
 
 # Including and linking ThermoPack
 
+*Note:* Before including ThermoPack in your project, ensure that you have compiled and installed ThermoPack by following the instructions for [building with cmake](source_build.html).
+
 An example `CMakeLists.txt` is found in `thermopack/addon/cppExamples`. In essence, all that is required to include ThermoPack is to add the following to your `CMakeLists.txt`:
 
 ```cmake
-set(THERMOPACK_HEADERS "path/to/thermopack/addon/cppThermopack")
-set(libthermopack_path "path/to/thermopack/addon/cppThermopack/libthermopack.[so/dylib]")
+set(THERMOPACK_DIR "/path/to/thermopack")
+find_library(THERMOPACK REQUIRED)
+
+add_executable(<my_program> <my_source.cpp>)
+add_library(<my_lib> <my_lib_source.cpp>)
+# etc.
 
-target_link_libraries(<my_project> ${libthermopack_path})
-target_include_directories(<my_project> ${THERMOPACK_HEADERS})
+target_link_libraries(<my_program> thermopack) # find_library ensures that the exported target "thermopack" is available
+target_link_libraries(<my_lib> thermopack)
+# etc.
 ```
 
-where `<my_project>` is some target previously defined by a call to `add_executable`, `add_library`, etc.
+The environment variable `THERMOPACK_DIR` can also be set using
+```bash
+export THERMOPACK_DIR=/path/to/thermopack
+```
+
+*When building on Linux* You should add the line
+```
+set_target_properties(<my_target> PROPERTIES 
+                            INSTALL_RPATH ${THERMOPACK_INSTALL_DIR}
+                            INSTALL_RPATH_USE_LINK_PATH TRUE)
+```
+to your `CMakeLists.txt`. This will add the `THERMOPACK_INSTALL_DIR` to your library/programs runtime search path (`@rpath`) and ensure that your binary will find the thermopack dynamic library at runtime if it is moved or installed to some other location than where it is built.
+
+and should point to the top-level directory of the thermopack-package (where `thermopack-config.cmake` is found). After the `find_library` command has been run, several convenience variables will have been defined:
+* `THERMOPACK_FOUND` : `TRUE` if thermopack was found, `FALSE` otherwise
+* `THERMOPACK_INSTALLED` : `TRUE` if thermopack is installed, `FALSE` otherwise
+* `THERMOPACK_ROOT` : Path to directory where `thermopack-config.cmake` was found
+* `THERMOPACK_INSTALLED_DIR` : Path to directory in which thermopack has installed files
+* `THERMOPACK_LIB` : Path to thermopack dynamic library
+* `THERMOPACK_STATIC_LIB` : Path to thermopack static library (archive)
+* `THERMOPACK_INCLUDE` : Path to thermopack C++ headers
+* `thermopack` : Imported shared library target with headers (this is what you want to link to using `target_link_libraries`)
 
 # Library structure
 
diff --git a/docs/vCurrent/source_build.md b/docs/vCurrent/source_build.md
index a7452a92..1b5a8e59 100644
--- a/docs/vCurrent/source_build.md
+++ b/docs/vCurrent/source_build.md
@@ -5,6 +5,19 @@ title: Installing the latest version of ThermoPack
 permalink: /vcurrent/source_build.html
 ---
 
+- [Using pip](#using-pip)
+- [Installing from wheels](#installing-from-wheels)
+- [Building from source](#building-from-source)
+  - [Prerequisites](#prerequisites)
+  - [CMake setup (macOS and Linux)](#cmake-setup-macos-and-linux)
+  - [CMake setup (Windows)](#cmake-setup-windows)
+- [Legacy build system (without CMake)](#legacy-build-system-without-cmake)
+  - [Linux setup](#linux-setup)
+  - [MacOS setup](#macos-setup)
+  - [Windows setup](#windows-setup)
+    - [MSYS2/Mingw-W64 setup](#msys2mingw-w64-setup)
+  - [Docker setup](#docker-setup)
+
 ## Using pip
 Thermopack has been compiled for Windows, Linux and macOS
 and made available on the [Python Package Index](https://pypi.org/project/thermopack/) (pypi), and can be
@@ -16,6 +29,9 @@ pip3 install thermopack
 
 For documentation on the version available on pypi, refer to the appropriate version number in the sidebar.
 
+## Installing from wheels
+Pre-built wheels for the latest version of ThermoPack on GitHub are available for download [here](). Refer to the linked page for instructions on how to install packages directly from a python wheel. Please note that the latest version on GitHub may be less stable, tested, and well documented than the versions distributed on PyPI.
+
 ## Building from source
 The following sections show how to fetch, compile and install Thermopack and
 the Python frontend pycThermopack. When things are properly installed, it may
@@ -34,6 +50,66 @@ Studio](https://visualstudio.microsoft.com/vs/). A solution file is found in
 assuming that the Intel Fortran compiler is integrated with Microsoft Visual
 Studio.
 
+For macOS and Linux, Lapack and Blas can likely be installed using `apt`, `brew`, or similar. For windows, Lapack will need to be built from source. The [CMake setup for Windows](#cmake-setup-windows) is configured to handle this automatically.
+
+### CMake setup (macOS and Linux)
+
+The `cmake`-based build system assumes that you have Lapack and gfortran installed, see above instructions for more on that.
+
+Build and install thermopack by running
+```bash
+mkdir build
+cd build
+cmake ..
+make install
+```
+
+This will ensure that the thermopack dynamic library is properly installed to `thermopack/installed` and `thermopack/addon/pycThermopack/thermopack`.
+
+To set up the python wrapper, 
+```bash
+python addon/pycThermopack/map_platform_specifics.py
+pip install addon/pycThermopack/
+```
+this will generate the file `addon/pycThermopack/thermopack/platform_specifics.py` and install thermopack to your activated virtual environment.
+
+ThermoPack can be configured to return computed properties as either tuples (`v2`) or using the `Property` struct (`v3`), this is toggled with
+the `-diffs` flag when running `map_platform_specifics.py` as
+```bash
+python map_platform_specifics.py -diffs=v2 # Use tuples
+python map_platform_specifics.py -diffs=v3 # use Property
+```
+the default value is `-diffs=v3`.
+
+### CMake setup (Windows)
+
+To compile thermopack (and Lapack) with intel fortran, first run
+```
+git submodule update --init --recursive
+```
+from within the `thermopack` direcory, in order to clone Lapack. Then, run
+```
+mkdir build
+cd build
+cmake .. -G Ninja -DCMAKE_Fortran_COMPILER=ifort -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=Release
+cmake --build . --config=Release --target install
+```
+Compile and install Lapack, and install the thermopack dynamic library to `thermopack/installed` and `thermopack/addon/pycThermopack/thermopack`.
+
+To configure and install the python-wrapper, run
+```
+python addon/pycThermopack/map_platform_specifics.py
+pip install addon/pycThermopack/
+```
+
+*Note:* If your thermopack dynamic library is called `libthermopack.dll`, and not `thermopack.dll`, you will instead need to run
+```
+python addon/pycThermopack/map_platform_specifics.py --ifort=True
+pip install addon/pycThermopack/
+```
+
+## Legacy build system (without CMake)
+
 ### Linux setup
 The Thermopack source code is downloaded by cloning the library to your local
 computer. The following commands assume that you have a local installation of
@@ -131,7 +207,4 @@ how to install pycThermopack for the MSYS2 environment.
 
 ### Docker setup
 See [addon/docker/README.md](https://github.com/thermotools/thermopack/tree/main/addon/docker) for
-available Dockerfiles to run Thermopack with docker.
-
-### CMake setup
-See [thermopack_cmake](https://github.com/morteham/thermopack_cmake) for prototype CMake scripts to compile Thermopack.
\ No newline at end of file
+available Dockerfiles to run Thermopack with docker.
\ No newline at end of file
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2f1dc662..eb501dd3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,3 +1,7 @@
+# Set FORTRAN compile flags for thermopack and lapack
+set(CMAKE_Fortran_FLAGS_RELEASE "${CMAKE_Fortran_FLAGS_RELEASE} ${tp_optim_flags}")
+set(CMAKE_Fortran_FLAGS_DEBUG "${CMAKE_Fortran_FLAGS_DEBUG} ${tp_debug_flags}")
+
 file(GLOB SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.f90)
 list(REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/thermopack.f90)
 set(TREND_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../addon/trend_interface/include)
@@ -5,30 +9,48 @@ set(TREND ${TREND_DIR}/trend_interface.f95)
 
 if(NOT MSVC)
     find_package(LAPACK REQUIRED)
+else()
+    if(POLICY CMP0077)
+        set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
+    endif()
+    # Disable single/complex/complex16 as it is not used by thermopack
+    set(BUILD_SINGLE OFF)
+    set(BUILD_COMPLEX OFF)
+    set(BUILD_COMPLEX16 OFF)
+    add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../external/lapack "lapack")
+    # Add flags not comatible with lapack
+    set(CMAKE_Fortran_FLAGS_RELEASE "${CMAKE_Fortran_FLAGS_RELEASE} /real-size:64 ")
+    set(CMAKE_Fortran_FLAGS_DEBUG "${CMAKE_Fortran_FLAGS_DEBUG} /real-size:64 /warn:all,noexternal /check:all,noarg_temp_created,nopointers")
 endif()
 
-# Define the executable in terms of the source files
 add_library(libthermopack STATIC ${SOURCES})
 if(MSVC)
+    set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
     add_library(thermopack SHARED ${SOURCES})
-    set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /libs:static /threads")
+    set_target_properties(thermopack PROPERTIES Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dynamic OUTPUT_NAME libthermopack)
     set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEF:${CMAKE_SOURCE_DIR}/MSVStudio/thermopack.def")
+    target_link_options(thermopack PRIVATE "/NODEFAULTLIB:libmmd" "/NODEFAULTLIB:MSVCRT" "/NODEFAULTLIB:libifcoremd" "/NODEFAULTLIB:svml_dispmd")
 else(MSVC)
     if(APPLE)
-        set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${CMAKE_SOURCE_DIR}/libthermopack_export.symbols")
+        set(tp_linker_flags "-exported_symbols_list ${CMAKE_CURRENT_SOURCE_DIR}/../libthermopack_export.symbols")
     else(APPLE)
-        set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${CMAKE_SOURCE_DIR}/libthermopack_export.version")
+        set(tp_linker_flags "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/../libthermopack_export.version")
     endif(APPLE)
     add_library(thermopack SHARED $<TARGET_OBJECTS:libthermopack>)
+    target_link_options(thermopack PRIVATE ${tp_linker_flags})
+    set_target_properties(thermopack PROPERTIES Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
 endif(MSVC)
-
-set_target_properties(libthermopack PROPERTIES Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+message(STATUS "Linker: ${CMAKE_LINKER}")
+set_target_properties(libthermopack PROPERTIES 
+                                    Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+                                    OUTPUT_NAME thermopack_static)
 target_include_directories(libthermopack PUBLIC ${TREND_DIR})
-set_target_properties(thermopack PROPERTIES Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+
 target_include_directories(thermopack PUBLIC ${TREND_DIR})
 
+install(TARGETS thermopack DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/../installed/)
+install(TARGETS libthermopack DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/../installed/)
 install(TARGETS thermopack DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/../addon/pycThermopack/thermopack)
-install(TARGETS thermopack DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/../addon/cppThermopack)
 
 add_executable(run_thermopack ${CMAKE_CURRENT_SOURCE_DIR}/thermopack.f90)
 target_include_directories(run_thermopack PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
@@ -37,6 +59,9 @@ if(NOT MSVC)
     target_link_libraries(thermopack ${LAPACK_LIBRARIES})
     target_link_libraries(run_thermopack libthermopack ${LAPACK_LIBRARIES})
 else()
-    target_link_libraries(thermopack PRIVATE lapack blas)
+    target_link_options(run_thermopack PRIVATE "/NODEFAULTLIB:MSVCRT")
+    target_link_libraries(thermopack PUBLIC lapack blas)
     target_link_libraries(run_thermopack libthermopack lapack blas)
 endif()
+
+install(TARGETS run_thermopack DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/../installed/)
diff --git a/src/eos.f90 b/src/eos.f90
index 3d66c068..41f76612 100644
--- a/src/eos.f90
+++ b/src/eos.f90
@@ -880,7 +880,7 @@ end subroutine ideal_entropy_single
   !>
   !> \author MH, 2014-01
   !----------------------------------------------------------------------
-  subroutine ideal_enthalpy_single(t,j,h,dhdt,dhdp)
+  subroutine ideal_enthalpy_single(t,j,h,dhdt)
     use ideal, only: Hideal_apparent, Cpideal_apparent
     use eos_parameters, only: single_eos
     implicit none
@@ -889,7 +889,6 @@ subroutine ideal_enthalpy_single(t,j,h,dhdt,dhdp)
     integer, intent(in) :: j                !< Component index
     real, intent(out) :: h                  !< J/mol - Ideal enthalpy
     real, optional, intent(out) :: dhdt     !< J/mol/K - Temperature differential of ideal enthalpy
-    real, optional, intent(out) :: dhdp     !< J/mol/Pa - Pressure differential of ideal enthalpy
     ! Locals
     type(thermo_model), pointer :: act_mod_ptr
     class(base_eos_param), pointer :: act_eos_ptr
@@ -925,9 +924,6 @@ subroutine ideal_enthalpy_single(t,j,h,dhdt,dhdp)
       write(*,*) 'EoSlib error in eos::idealEnthalpySingle: No such EoS libray:',act_mod_ptr%EosLib
       call stoperror('')
     end select
-    if (present(dhdp)) then
-      dhdp=0.0
-    end if
   end subroutine ideal_enthalpy_single
 
   !----------------------------------------------------------------------
diff --git a/thermopack-config.cmake b/thermopack-config.cmake
index 11b3a081..fff0abe9 100644
--- a/thermopack-config.cmake
+++ b/thermopack-config.cmake
@@ -8,23 +8,32 @@
 
 
 message(STATUS "ThermoPack found at: ${CMAKE_CURRENT_LIST_DIR}")
-set(THERMOPACK_LIB ${CMAKE_CURRENT_LIST_DIR}/addon/cppThermopack/libthermopack${CMAKE_SHARED_LIBRARY_SUFFIX})
+set(THERMOPACK_LIB ${CMAKE_CURRENT_LIST_DIR}/installed/libthermopack${CMAKE_SHARED_LIBRARY_SUFFIX})
 set(THERMOPACK_ROOT ${CMAKE_CURRENT_LIST_DIR})
 
 if(NOT EXISTS ${THERMOPACK_LIB})
     unset(THERMOPACK_LIB)
     set(THERMOPACK_INSTALLED FALSE)
     message(STATUS "ThermoPack has not been properly installed.")
+    message(STATUS "Setting ThermoPack cmake variables ...")
+    message(STATUS "THERMOPACK_INSTALLED: ${THERMOPACK_INSTALLED}")
+    message(STATUS "THERMOPACK_ROOT: ${THERMOPACK_ROOT}")
     return()
 endif()
 
+set(THERMOPACK_STATIC_LIB ${CMAKE_CURRENT_LIST_DIR}/installed)
+set(THERMOPACK_STATIC_LIB ${CMAKE_CURRENT_LIST_DIR}/installed/libthermopack${CMAKE_STATIC_LIBRARY_SUFFIX})
+
 set(THERMOPACK_FOUND TRUE)
 set(THERMOPACK_INSTALLED TRUE)
 set(THERMOPACK_INCLUDE ${CMAKE_CURRENT_LIST_DIR}/addon/cppThermopack)
 
 message(STATUS "Setting ThermoPack cmake variables ...")
+message(STATUS "THERMOPACK_INSTALLED: ${THERMOPACK_INSTALLED}")
 message(STATUS "THERMOPACK_ROOT: ${THERMOPACK_ROOT}")
+message(STATUS "THERMOPACK_INSTALL_DIR: ${THERMOPACK_INSTALL_DIR}")
 message(STATUS "THERMOPACK_LIB: ${THERMOPACK_LIB}")
+message(STATUS "THERMOPACK_STATIC_LIB: ${THERMOPACK_STATIC_LIB}")
 message(STATUS "THERMOPACK_INCLUDE: ${THERMOPACK_INCLUDE}")
 
 if(NOT TARGET thermopack)


Test patch:
diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml
new file mode 100644
index 00000000..070d8fc8
--- /dev/null
+++ b/.github/workflows/unittests.yml
@@ -0,0 +1,200 @@
+name: unittests
+
+on:
+  push:
+      branches:
+          - main
+  pull_request:
+
+jobs:
+  run-tests:
+    name: Test on ${{ matrix.os }} with ${{ matrix.toolchain.compiler }} ${{ matrix.toolchain.version }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-latest, macos-latest, macos-12]
+        toolchain:
+          - {compiler: gcc, version: 13}
+        include:
+          - os: windows-2022
+            toolchain: {compiler: intel-classic, version: '2021.12'}
+
+    steps:
+      - uses: actions/checkout@v4
+      - name: checkout submodules
+        run: git submodule update --init --recursive
+
+      # - name: Setup Visual Studio 2022
+      #   if: matrix.os == 'windows-2022'
+      #   uses: microsoft/setup-msbuild@v1.0.2
+
+      - name: Install ninja-build tool
+        if: matrix.os == 'windows-2022'
+        uses: seanmiddleditch/gha-setup-ninja@16b940825621068d98711680b6c3ff92201f8fc0
+
+      - uses: fortran-lang/setup-fortran@v1
+        id: setup-fortran
+        if: matrix.os != 'macos-12'
+        with:
+          compiler: ${{ matrix.toolchain.compiler }}
+          version: ${{ matrix.toolchain.version }}
+      
+      - name: Setup gfortran macos-12
+        if: matrix.os == 'macos-12'
+        run: |
+          export FC=$(which gfortran-13)
+          echo FC=$FC >> $GITHUB_ENV
+
+      - name: Build ThermoPack
+        if: matrix.os != 'windows-2022'
+        run: |
+          mkdir build
+          cd build
+          cmake -Dtest=ON -DCMAKE_BUILD_TYPE=Debug ..
+          make -j4 install
+      
+      # - name: Build LAPACK Windows
+      #   if: matrix.os == 'windows-2022'
+      #   run: |
+      #     cd external
+      #     cd lapack
+      #     cmake -B build -G Ninja -DCMAKE_Fortran_COMPILER=${{ env.FC }} -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icx
+      #     cmake --build build --target install
+
+      - name: Build ThermoPack Windows
+        if: ${{ runner.os == 'Windows' }}
+        run: |
+          echo "Building on ${{ matrix.os }}"
+          $FC = ${{ env.FC }}
+          $FC_PATH = $((Get-Command ${{ env.FC }} | Select-Object -ExpandProperty Path) -replace '\\', '/')
+          echo "Using compiler $env:FC / ${{ env.FC }}"
+          echo "Fortran compiler at: $(Get-Command ${{ env.FC }} | Select-Object -ExpandProperty Path) / $FC_PATH"
+          cmake -B build -G Ninja -DCMAKE_Fortran_COMPILER=${{ env.FC }} -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=Release 
+          cmake --build build --config Release --target install --verbose
+      # cmake -B build -G "Visual Studio 17 2022" -DCMAKE_Fortran_COMPILER=${{ env.FC }} 
+      
+      - name: Inspect thermopack Linux
+        if: ${{ runner.os == 'Linux' }}
+        run: | 
+          echo "--- Inspecting libthermopack ---"
+          ldd installed/libthermopack.so
+          nm installed/libthermopack.so | grep " T "
+          echo "--- Inspecting run_unittests ---"
+          ldd installed/unittests
+
+      - name: Inspect thermopack MacOS
+        if: ${{ runner.os == 'macOS' }}
+        run: | 
+          echo "--- Inspecting libthermopack ---"
+          otool -L installed/libthermopack.dylib
+          nm installed/libthermopack.dylib | grep " T "
+          echo "--- Inspecting run_unittests ---"
+          otool -L installed/unittests
+
+      - name: Inspect thermopack Windows
+        if: ${{ runner.os == 'Windows' }}
+        run: |
+          echo "--- Inspecting libthermopack.dll ---"
+          dumpbin /DEPENDENTS installed/libthermopack.dll
+          dumpbin /EXPORTS installed/libthermopack.dll
+
+      - name: Upload
+        uses: actions/upload-artifact@v4
+        with:
+            name: binary-${{ matrix.os }}-${{ matrix.toolchain.compiler }}
+            path: ./installed/*
+    
+      - id: fortran_test
+        name: Run unittests
+        if: matrix.os != 'windows-2022'
+        continue-on-error: true
+        run: |
+            result=$(./installed/unittests)
+            if echo "$result" | grep -q "OK"; then
+            statuscode=0
+            else
+            statuscode=1
+            fi
+            echo "$result"
+            echo "statuscode=$statuscode" >> $GITHUB_OUTPUT
+            exit $statuscode
+            
+      - uses: actions/setup-python@v5
+      - id: python_test 
+        name: Run python tests for Linux/MacOS
+        if: matrix.os != 'windows-2022'
+        continue-on-error: true
+        run: |
+            python addon/pycThermopack/map_platform_specifics.py
+            pip install addon/pycThermopack/[test]
+            pytest addon/pyTests
+            result=$(pytest addon/pyTests)
+            if echo "$result" | grep -q "FAILED"; then
+            statuscode=1
+            else
+            statuscode=0
+            fi
+            echo "$result"
+            echo "statuscode=$statuscode" >> $GITHUB_OUTPUT
+            exit $statuscode
+    
+      - uses: actions/setup-python@v5
+        with:
+          python-version: '3.11'
+      - id: python_test_windows
+        name: Run python tests for Windows
+        if: matrix.os == 'windows-2022'
+        continue-on-error: true
+        run: |
+            python -m pip install --upgrade pip
+            pip install pytest numpy
+            python -c "import sys; sys.path.insert(0, './addon/pycThermopack'); import makescript; makescript.windows_make('v3')"
+            pip install ./addon/pycThermopack/[test]
+            $result = python -m pytest ./addon/pyTests
+            if ($result -match "FAILED") {
+              $statuscode = 1
+            } else {
+              $statuscode = 0
+            }
+            Write-Output $result
+            Add-Content -Path $env:GITHUB_OUTPUT -Value "statuscode=$statuscode"
+            exit $statuscode
+
+      - name: Run cppExamples on Linux/macOS
+        id: cpp_test
+        if: matrix.os != 'windows-2022'
+        run: |
+          cd addon/cppExamples
+          mkdir build
+          cd build
+          cmake ..
+          make
+          examples=("flashes" "saturation" "tp_properties" "tv_properties")
+          nfailed=0
+          for exmp in "${examples[@]}"; do echo "Running $exmp"; ./"$exmp" > /dev/null; if [[ $? -ne 0 ]]; then echo "$exmp exited with code $? (failure)."; ((nfailed++)); else echo "$exmp exit 0 (success)"; fi; done
+          echo "statuscode=$nfailed" >> $GITHUB_OUTPUT
+          exit $nfailed
+      
+      - name: Check success
+        if: matrix.os != 'windows-2022'
+        run: |
+            echo "Fortran test result : ${{ steps.fortran_test.outputs.statuscode }}"
+            echo "Python test result : ${{ steps.python_test.outputs.statuscode }}"
+            echo "C++ test result : " ${{ steps.cpp_test.outputs.statuscode }}
+            if [[ ${{ steps.fortran_test.outputs.statuscode }} -eq 0 ]] && [[ ${{ steps.python_test.outputs.statuscode }} -eq 0 ]] && [[ ${{ steps.cpp_test.outputs.statuscode }} -eq 0 ]]; then
+            exit 0
+            else
+            exit 1
+            fi
+
+      - name: Check success Windows
+        if: matrix.os == 'windows-2022'
+        run: |
+            $statusCode = ${{ steps.python_test_windows.outputs.statuscode }}
+            Write-Host "Python test result: $statusCode"
+            if ($statusCode -eq 0) {
+              exit 0
+            } else {
+              exit 1
+            }
diff --git a/addon/pyTests/test_issues.py b/addon/pyTests/test_issues.py
index 35c0c945..da43d781 100644
--- a/addon/pyTests/test_issues.py
+++ b/addon/pyTests/test_issues.py
@@ -5,12 +5,17 @@
 When an issue is resolved, a test with code that triggered it before resolution should be added here.
 """
 from tools import ALL_CUBIC, check_eq
+from thermopack.platform_specifics import DIFFERENTIAL_RETURN_MODE
 from pytest import mark
 
 @mark.parametrize('eos', ALL_CUBIC)
 def test_pr_114(eos):
     T = 300
     eos = eos('ETOH,C2')
-    h1 = eos.idealenthalpysingle(1, T)
+    if DIFFERENTIAL_RETURN_MODE == 'v3':
+        h1 = eos.idealenthalpysingle(1, T)
+    elif DIFFERENTIAL_RETURN_MODE == 'v2':
+        h1, = eos.idealenthalpysingle(1, T)
+    
     h2, dh = eos.idealenthalpysingle(1, T, dhdt=True)
     assert check_eq(h1, h2)
\ No newline at end of file
diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt
index e54361ae..e443b18e 100644
--- a/unittests/CMakeLists.txt
+++ b/unittests/CMakeLists.txt
@@ -1,8 +1,17 @@
 
-set(CMAKE_Fortran_FLAGS ${tp_fortran_flags})
+set(CMAKE_Fortran_FLAGS_RELEASE "${CMAKE_Fortran_FLAGS_RELEASE} ${tp_optim_flags}")
+set(CMAKE_Fortran_FLAGS_DEBUG "${CMAKE_Fortran_FLAGS_DEBUG} ${tp_debug_flags}")
 
 enable_testing()
 file(GLOB UNITTEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.pf)
 list(REMOVE_ITEM UNITTEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/test_trend.pf)
-add_pfunit_ctest(unittests TEST_SOURCES ${UNITTEST_SRC} LINK_LIBRARIES thermopack)
-target_include_directories(unittests PRIVATE ${CMAKE_BINARY_DIR}/thermopack)
\ No newline at end of file
+add_pfunit_ctest(unittests TEST_SOURCES ${UNITTEST_SRC} LINK_LIBRARIES libthermopack lapack blas)
+target_include_directories(unittests PRIVATE ${CMAKE_BINARY_DIR}/thermopack)
+
+if (NOT MSVC AND NOT APPLE)
+    set_target_properties(unittests PROPERTIES 
+                            INSTALL_RPATH ${CMAKE_CURRENT_SOURCE_DIR}/../installed
+                            INSTALL_RPATH_USE_LINK_PATH TRUE)
+endif()
+
+install(TARGETS unittests DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/../installed/)
diff --git a/unittests/README.md b/unittests/README.md
index 51df9e94..658e8058 100644
--- a/unittests/README.md
+++ b/unittests/README.md
@@ -2,6 +2,23 @@
 
 ## Compiling and Running tests
 
+### With `cmake`
+
+The project is set up to search for `pFUnit` and use your installation if you have it installed. Set the environment variable `PFUNIT_DIR` before building if you have already installed `pFUnit`. If `pFUnit` is not found, the version of `pFUnit` under `external/pFUnit` will be built and used.
+
+Build and run unittests by executing
+
+```bash
+mkdir build
+cd build
+cmake -Dtest=ON ..
+make
+./unittests/run_unittests
+```
+
+from the ThermoPack root directory (`..`). Note that the value of the `test` option is cached, so if you have previously built ThermoPack, you may need to clear the `CMakeCache.txt` in the build directory.
+
+### With only `make`
 The test-suite is set up and maintained to be compatible with the [`pFUnit` fork under the ThermoTools project](https://github.com/thermotools/pFUnit). To build the test suite you must first clone and build `pFUnit`.
 
 To compile the test-suite the environment variable `PFUNIT_DIR` must be set to the path of the directory containing the directories `PFUNIT-4.x` and `FARGPARSE-1.x`, where `x` is some subversion number. This corresponds to running