Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1 +1 @@
global-include cLIBFMS/lib/libcFMS.*
graft pyfms/lib
47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,46 @@
# fms2py
# **`fms2py`**

## **Installation**
pyFMS requires a compiled `cFMS` library and the pyFMS repository contains
cFMS as a submodule. If `cFMS` is not installed on the user's
local system, the library can be installed with

```
1. git clone --recursive https://github.com/NOAA-GFDL/pyFMS.git
2. cd pyFMS
3. emacs ./compile.py (see Section compile.py)
4. python ./compile.py
```

The script `compile.py` will first compile and install the FMS library (which is
a submodule in cFMS) to `pyfms/lib/FMS`. Then, compile.py will compile the cFMS library
linking to FMS in `pyfms/lib/FMS`. cFMS will be installed to `pyfms/lib/cFMS`.

Upon `import pyfms`, pyFMS will automatically load the cFMS library
in `pyfms/lib/cFMS`. If the cFMS library does not exist, or if users wish to load a
diferent instance of cFMS, the following should be set in the program before invoking
any pyFMS methods:

```
import pyfms
pyfms.cfms.init(libpath=path_to_cfms/libcFMS.so)
```

## compile.py
To compile cFMS with the script `compile.py`, users will need to specify the following
fields:

1. Fortran and C compilers, for example, as shown below:

```
FC = "mpif90"
CC = "mpicc"
Comment on lines +37 to +38
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires an MPI library be accessible. You'll need to mention that as well. Is there any chance this could become an issue if mpi4py and the MPI used here are different flavors?

```

2. Path to the libyaml and netCDF installations, for example, as shown below:

```
yaml = "/opt/libyaml/0.2.5/GNU/14.2.0/"
netcdf = "/opt/netcdf/4.9.3/GNU/14.2.0/"
```
100 changes: 100 additions & 0 deletions compile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import os
import subprocess


# path to the installed yaml library
yaml = "/opt/libyaml/0.2.5/GNU/14.2.0/"

# path to the installed netcdf library
netcdf = "/opt/netcdf/4.9.3/GNU/14.2.0/"
Comment on lines +5 to +9
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not ideal as they are hard-coded for a specific versions and a specific machine. I know this is addressed in the Readme.


# Fortran compiler
FC = "mpif90"

# C compiler
CC = "mpicc"

# default Fortran compiler flags
FMS_FCFLAGS = f"-I{yaml}/include -I{netcdf}/include -fPIC"

# default C compiler flags
FMS_CFLAGS = f"-I{yaml}/include -I{netcdf}/include -fPIC"

# library flags
FMS_LDFLAGS = f"-L{yaml}/lib -L{netcdf}/lib -lnetcdf"

cFMS_FCFLAGS = "-fPIC"
cFMS_CFLAGS = "-fPIC"
cFMS_LDFLAGS = ""

# current directory
currdir = os.path.dirname(__file__)

# absolute path to cFMS submodule
cFMS = f"{currdir}/cFMS"

# absolute path to FMS submodule
FMS = f"{cFMS}/FMS"

# absolue path to install libraries
cFMS_install = f"{currdir}/pyfms/lib/cFMS"
FMS_install = f"{currdir}/pyfms/lib/FMS"


def compile_FMS():

"""
Install FMS to FMS_install
"""

currdir = os.path.dirname(__file__)
os.chdir(FMS)

subprocess.run(["autoreconf", "-iv"])
subprocess.run(
[
"./configure",
"--enable-portable-kinds",
"--with-yaml",
f"FC={FC}",
f"CC={CC}",
f"FCFLAGS={FMS_FCFLAGS}",
f"CFLAGS={FMS_CFLAGS}",
f"LDFLAGS={FMS_LDFLAGS}",
f"--prefix={FMS_install}",
]
)
subprocess.run(["make", "install"])

os.chdir(currdir)


def compile_cFMS():

"""
Install cFMS to cFMS_install
"""
currdir = os.path.dirname(__file__)
os.chdir(cFMS)

subprocess.run(["autoreconf", "-iv"])
subprocess.run(
[
"./configure",
f"--with-fms={FMS_install}",
f"FC={FC}",
f"CC={CC}",
f"FCFLAGS={cFMS_FCFLAGS}",
f"CFLAGS={cFMS_CFLAGS}",
f"LDFLAGS={cFMS_LDFLAGS}",
f"--prefix={cFMS_install}",
]
)
subprocess.run(["make", "install"])

os.chdir(currdir)


# compile
compile_FMS()
compile_cFMS()
28 changes: 0 additions & 28 deletions compile_c_libs.sh

This file was deleted.

16 changes: 13 additions & 3 deletions pyfms/cfms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import pyfms


_libpath: str = os.path.dirname(__file__) + "/cLIBFMS/lib/libcFMS.so"
_lib: type[ctypes.CDLL] = ctypes.cdll.LoadLibrary(_libpath)
_libpath = None
_lib = None


def init(libpath: str = None):
Expand All @@ -21,7 +21,17 @@ def init(libpath: str = None):

global _libpath, _lib

if libpath is not None:
if libpath is None:
_libpath = os.path.dirname(__file__) + "/lib/cFMS/lib/libcFMS.so"
try:
_lib = ctypes.cdll.LoadLibrary(_libpath)
except OSError:
print(
f"{_libpath} does not exist. Please compile cFMS with ./compile.py\
or provide a path to cFMS with pyfms.cfms.init(libpath=path_to_cfms"
)
return
else:
_libpath = libpath
_lib = ctypes.cdll.LoadLibrary(_libpath)

Expand Down
2 changes: 0 additions & 2 deletions run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ function run_test() {
eval $1
if [ $? -ne 0 ] ; then exit 1 ; fi }

run_test "python -m pytest tests/test_build.py"

test="tests/test_fms.py"
create_input $test
run_test "python -m pytest -m parallel $test"
Expand Down
35 changes: 11 additions & 24 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,22 @@
import subprocess
from typing import List

from setuptools import find_namespace_packages, setup
from setuptools.command.build import build


class CustomBuild(build):
def run(self):
with open("install.log", "w") as f:
subprocess.run(["./compile_c_libs.sh"], stdout=f)
build.run(self)


test_requirements = ["pytest", "pytest-subtests", "coverage"]
test_requirements = [
"pytest",
"pytest-subtests",
"coverage",
"xarray",
"netCDF4",
"h5netcdf",
]
develop_requirements = test_requirements + ["pre-commit"]

extras_requires = {
"test": test_requirements,
"develop": develop_requirements,
}

requirements: List[str] = [
"dacite",
"h5netcdf",
"numpy",
"pyyaml",
"mpi4py",
"xarray",
"netcdf4",
]
requirements = ["dacite", "numpy", "pyyaml", "mpi4py"]

setup(
author="NOAA/GFDL",
Expand All @@ -45,9 +33,8 @@ def run(self):
name="pyfms",
license="",
packages=find_namespace_packages(include=["pyfms", "pyfms.*"]),
cmdclass={"build": CustomBuild},
include_package_data=True,
url="https://github.com/fmalatino/pyFMS.git",
version="2024.02.0",
url="https://github.com/NOAA-GFDL/pyFMS.git",
version="2024.12.0",
zip_safe=False,
)
7 changes: 0 additions & 7 deletions tests/test_build.py

This file was deleted.

42 changes: 5 additions & 37 deletions tests/test_init.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
import os
import sys

import pytest

import pyfms


curr_dir = os.path.dirname(os.path.abspath(__file__))
par_dir = os.path.dirname(curr_dir)
def test_library_loaded():

"""
Test to ensure library loaded automatically
"""

def test_write_module():
myfile = open("module1.py", "w")
myfile.write(
"""
import pyfms
class Module1Class():
module1_lib_id = id(pyfms.cfms.lib())
"""
)
myfile.close()
assert pyfms.cfms._lib is not None


def test_share_same_library():
Expand All @@ -32,20 +22,6 @@ def test_share_same_library():
assert id(pyfms.cfms._lib) == id(pyfms.mpp_domains._lib)


def test_load_library_same_object():
sys.path.append(par_dir)

"""
Test to ensure the ctypes CDLL Library object
is instantiated only once
"""

import module1

myclass = module1.Module1Class()
assert id(pyfms.cfms.lib()) == myclass.module1_lib_id


@pytest.mark.xfail
def test_library_load_fail():

Expand All @@ -55,11 +31,3 @@ def test_library_load_fail():
"""

pyfms.cfms.changelib(libpath="do_not_exist")


def test_remove_module():
os.remove("module1.py")


if __name__ == "__main__":
test_write_module()
Loading