Skip to content

Commit 55203f3

Browse files
Build the MUSICA library in CAM-SIMA (#364)
Tag name (required for release branches): Originator(s): @boulderdaze Description (include the issue title, and the keyword ['closes', 'fixes', 'resolves'] followed by the issue number): - Build the MUSICA library in CAM-SIMA as part of the #321 to enable the full implementation of MICM and TUV. - Update the Dockerfiles to include a newer version of ESMF that is the same version used by Derecho, and update the contents to use MUSICA schemes. - Update the README instructions for downloading submodules. - Tested case build using Docker and on Derecho. Describe any changes made to build system: Added functions to build musica in `cime_config/buildlib` Describe any changes made to the namelist: N/A List any changes to the defaults for the input datasets (e.g. boundary datasets): N/A List all files eliminated and why: N/A List all files added and what they do: ``` A cime_config/atm_musica_config.py ``` List all existing files that have been modified, and describe the changes: (Helpful git command: `git diff --name-status development...<your_branch_name>`) ``` M .dockerignore M .gitmodules M README.md M cime_config/buildlib M cime_config/buildnml M cime_config/cam_autogen.py M cime_config/cam_config.py M docker/Dockerfile M docker/Dockerfile.esmf M docker/Dockerfile.musica M src/physics/ncar_ccpp M src/physics/utils/musica_ccpp_dependencies.meta M test/unit/python/test_cam_autogen.py ``` If there are new failures (compared to the `test/existing-test-failures.txt` file), have them OK'd by the gatekeeper, note them here, and add them to the file. If there are baseline differences, include the test and the reason for the diff. What is the nature of the change? Roundoff? N/A derecho/intel/aux_sima: N/A derecho/gnu/aux_sima: N/A If this changes climate describe any run(s) done to evaluate the new climate in enough detail that it(they) could be reproduced: N/A CAM-SIMA date used for the baseline comparison tests if different than latest: --------- Co-authored-by: Jesse Nusbaumer <nusbaume@ucar.edu>
1 parent dffdfcd commit 55203f3

14 files changed

+257
-65
lines changed

.dockerignore

+10-13
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
# ignore all
2-
*
1+
.github/
2+
.gitignore
3+
LICENSE
4+
README.md
35

4-
# include things to copy
5-
!Externals*
6-
!src/
7-
!cime_config/
8-
!manage_externals/
9-
!test/
10-
!.config_files.xml
11-
!docker
12-
!bin/
13-
!.lib/
14-
!.gitmodules
6+
# Ignore editor temporaries and backups
7+
*.swp
8+
*~
9+
.#*
10+
\#*#
11+
**/.vscode/

.gitmodules

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
[submodule "ncar-physics"]
2121
path = src/physics/ncar_ccpp
2222
url = https://github.com/ESCOMP/atmospheric_physics
23-
fxtag = 252b500a93c89f36ece7d8ba08fd8eb025279eaa
23+
fxtag = 37224423f62d9a71922cb6fe9beca1516d9403a4
2424
fxrequired = AlwaysRequired
2525
fxDONOTUSEurl = https://github.com/ESCOMP/atmospheric_physics
2626
[submodule "ccs_config"]

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ cd CAM-SIMA
1616
## NOTE: This is **unsupported** development code and is subject to the [CESM developer's agreement](http://www.cgd.ucar.edu/cseg/development-code.html).
1717
```
1818
git checkout development
19-
./manage_externals/checkout_externals
19+
./bin/git-fleximod update
2020
```
2121

2222
Good luck, and have a great day!

cime_config/atm_musica_config.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""
2+
The URLs and tags provided in this script are read by buildlib to build
3+
and install the MUSICA library, as well as to download the MUSICA configuration.
4+
"""
5+
6+
MUSICA_CCPP_SCHEME_NAME = "musica_ccpp"
7+
MUSICA_REPO_URL = "https://github.com/NCAR/musica.git"
8+
MUSICA_TAG = "cc39bb00d2220fc81c85b22d3ceea4a39bd2bacf"
9+
CHEMISTRY_DATA_REPO_URL = "https://github.com/NCAR/cam-sima-chemistry-data.git"
10+
CHEMISTRY_DATA_TAG = "2b58f2410ec7a565bcf80dee16ec20f6bc35d78b"

cime_config/buildlib

+207-7
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ import sys
88
import os
99
import filecmp
1010
import shutil
11+
import subprocess
1112
import logging
1213

1314
from cam_config import ConfigCAM # CAM's configure structure
15+
from atm_musica_config import MUSICA_CCPP_SCHEME_NAME
16+
from atm_musica_config import MUSICA_REPO_URL, MUSICA_TAG
17+
from atm_musica_config import CHEMISTRY_DATA_REPO_URL, CHEMISTRY_DATA_TAG
1418

1519
# Check for the CIME library, and add it
1620
# to the python path:
@@ -27,6 +31,7 @@ from CIME.utils import stop_buffering_output
2731
from CIME.buildlib import parse_input
2832
from CIME.build import get_standard_makefile_args
2933
from CIME.Tools.standard_script_setup import check_minimum_python_version
34+
from CIME.locked_files import lock_file, unlock_file
3035
#pylint: enable=wrong-import-position
3136

3237
check_minimum_python_version(3, 7) #CAM requires version 3.7 or greater
@@ -73,7 +78,7 @@ def _build_cam():
7378
# Re-run source generator in case registry, CCPP suites, or
7479
# generator scripts have been modified, and
7580
# to extract required source code paths:
76-
config.generate_cam_src(gen_indent)
81+
scheme_names = config.generate_cam_src(gen_indent)
7782

7883
dycore = config.get_value('dyn')
7984
reg_dir = config.get_value('reg_dir')
@@ -122,12 +127,6 @@ def _build_cam():
122127
paths.append(os.path.join(atm_root, "src", "dynamics", "tests",
123128
"initial_conditions"))
124129

125-
# If using the CMEPS/NUOPC coupler, then add additional path:
126-
if case.get_value("COMP_INTERFACE") == "nuopc":
127-
paths.append(os.path.join(__CIMEROOT, "src", "drivers",
128-
"nuopc", "nuopc_cap_share"))
129-
# End if
130-
131130
# Write Filepath text file
132131
with open(filepath_src, "w", encoding='utf-8') as filepath:
133132
filepath.write("\n".join(paths))
@@ -174,11 +173,32 @@ def _build_cam():
174173
if len(optional_mpas_features) > 0:
175174
cmd += " OPTIONAL_MPAS_FEATURES=\"" + " ".join(optional_mpas_features) + "\""
176175

176+
# If MUSICA-CCPP scheme is used in a suite, download
177+
# the MUSICA configuration and build the MUSICA library
178+
if MUSICA_CCPP_SCHEME_NAME in scheme_names:
179+
_download_musica_configuration(caseroot)
180+
musica_install_path = _build_musica(caseroot)
181+
182+
cam_linked_libs = case.get_value("CAM_LINKED_LIBS")
183+
musica_libs = "-lmusica-fortran -lmusica -lyaml-cpp"
184+
if not musica_libs in cam_linked_libs:
185+
_set_musica_lib_path(musica_install_path, caseroot)
186+
187+
cmd += ' USER_INCLDIR="'\
188+
f'-I{os.path.join(musica_install_path, "include", "micm")} '\
189+
f'-I{os.path.join(musica_install_path, "include", "musica")} '\
190+
f'-I{os.path.join(musica_install_path, "include", "musica", "micm")} '\
191+
f'-I{os.path.join(musica_install_path, "include", "musica", "tuvx")} '\
192+
f'-I{os.path.join(musica_install_path, "include", "musica", "fortran")} '\
193+
'"'
194+
177195
retcode, out, err = run_cmd(cmd)
178196
_LOGGER.info("Command %s:\n\nstdout:\n%s\n\nstderr:\n%s\n", cmd, out, err)
179197
expect(retcode == 0, f"Command {cmd} failed with rc={retcode}")
180198

199+
###############################################################################
181200
def _prepare_mpas(case: Case) -> None:
201+
###############################################################################
182202
"""
183203
Prepare MPAS build infrastructure.
184204
"""
@@ -206,7 +226,9 @@ def _prepare_mpas(case: Case) -> None:
206226
shutil.copytree(os.path.normpath(os.path.join(mpas_dycore_src_root, os.pardir, os.pardir, "assets")), mpas_dycore_bld_root, copy_function=_copy2_as_needed, dirs_exist_ok=True)
207227
shutil.copytree(os.path.normpath(os.path.join(mpas_dycore_src_root, os.pardir, os.pardir, "driver")), os.path.join(mpas_dycore_bld_root, "driver"), copy_function=_copy2_as_needed, dirs_exist_ok=True)
208228

229+
###############################################################################
209230
def _copy2_as_needed(src: str, dst: str) -> None:
231+
###############################################################################
210232
"""
211233
Wrapper around `shutil.copy2`. Copy the file `src` to the file or directory `dst` as needed.
212234
"""
@@ -226,6 +248,184 @@ def _copy2_as_needed(src: str, dst: str) -> None:
226248
# Example scenario: User added some new source code files.
227249
shutil.copy2(src, dst)
228250

251+
###############################################################################
252+
def _build_musica(clone_dest: str) -> str:
253+
###############################################################################
254+
"""
255+
Builds and installs the MUSICA library.
256+
257+
Args:
258+
clone_dest: destination where the repository will be cloned
259+
260+
Raises:
261+
Exception: If configuring the CMake MUSICA project fails or
262+
the MUSICA library build fails, an exception is raised.
263+
264+
Returns:
265+
musica_install_path: path to the MUSICA installation directory
266+
"""
267+
_clone_and_checkout(MUSICA_REPO_URL, MUSICA_TAG, clone_dest)
268+
269+
bld_path = os.path.join(clone_dest, "musica", "build")
270+
if os.path.exists(bld_path):
271+
shutil.rmtree(bld_path)
272+
os.makedirs(bld_path)
273+
274+
install_dir = "install"
275+
command = [
276+
"cmake",
277+
f"-D CMAKE_INSTALL_PREFIX={install_dir}",
278+
"-D CMAKE_BUILD_TYPE=Release",
279+
"-D MUSICA_ENABLE_TESTS=OFF",
280+
"-D MUSICA_BUILD_FORTRAN_INTERFACE=ON",
281+
".."
282+
]
283+
try:
284+
subprocess.run(command, cwd=bld_path, stdout=subprocess.PIPE,
285+
stderr=subprocess.PIPE, text=True, check=False)
286+
except subprocess.CalledProcessError as e:
287+
raise subprocess.CalledProcessError(e.returncode, e.cmd, "The subprocess \
288+
for cmake to configure the MUSICA CMake project failed.") from e
289+
except FileNotFoundError as e:
290+
raise FileNotFoundError("The 'cmake' command was not found.") from e
291+
except OSError as e:
292+
raise OSError("An error occurred while executing the 'cmake' command.") from e
293+
294+
command = ["cmake", "--build", ".", "--target", "install"]
295+
try:
296+
subprocess.run(command, cwd=bld_path, stdout=subprocess.PIPE,
297+
stderr=subprocess.PIPE, text=True, check=False)
298+
except subprocess.CalledProcessError as e:
299+
raise subprocess.CalledProcessError(e.returncode, e.cmd, "The subprocess \
300+
for cmake to build the MUSICA library failed.") from e
301+
except FileNotFoundError as e:
302+
raise FileNotFoundError("The 'cmake' command was not found.") from e
303+
except OSError as e:
304+
raise OSError("An error occurred while executing the 'cmake' command.") from e
305+
306+
musica_install_path = os.path.join(bld_path, install_dir)
307+
308+
return musica_install_path
309+
310+
###############################################################################
311+
def _download_musica_configuration(download_dest: str) -> None:
312+
###############################################################################
313+
"""
314+
Downloads the MUSICA configuration and renames the configuration
315+
directory to match the name in the MUSICA-CCPP configuration.
316+
317+
Args:
318+
download_dest: destination where configuration will be downloaded
319+
320+
Raises:
321+
Exception: If the directory to be renamed is not found or
322+
any other exceptions occur during the renaming process,
323+
an exception is raised with the error message.
324+
"""
325+
musica_config_dir_name = "musica_configurations"
326+
327+
_clone_and_checkout(CHEMISTRY_DATA_REPO_URL, CHEMISTRY_DATA_TAG, download_dest)
328+
329+
original_dir = os.path.join(download_dest, "cam-sima-chemistry-data", "mechanisms")
330+
renamed_dir = os.path.join(download_dest, "cam-sima-chemistry-data", musica_config_dir_name)
331+
try:
332+
os.rename(original_dir, renamed_dir)
333+
except FileNotFoundError as e:
334+
raise FileNotFoundError(f"The directory '{original_dir}' was not found.") from e
335+
except FileExistsError as e:
336+
raise FileExistsError(f"The destination directory '{renamed_dir}' already exists.") from e
337+
except PermissionError as e:
338+
raise PermissionError(f"Permission denied to rename '{original_dir}'.") from e
339+
except OSError as e:
340+
raise OSError("An error occurred while renaming.") from e
341+
342+
musica_config_path = os.path.join(download_dest, musica_config_dir_name)
343+
if os.path.exists(musica_config_path):
344+
shutil.rmtree(musica_config_path)
345+
346+
shutil.move(renamed_dir, download_dest)
347+
348+
###############################################################################
349+
def _set_musica_lib_path(musica_install_path: str, caseroot: str) -> None:
350+
###############################################################################
351+
"""
352+
Sets the MUSICA libraries path to CAM_LINKED_LIBS, allowing the libraries
353+
to be linked during the CESM build process.
354+
355+
Args:
356+
musica_install_path: path to the MUSICA installation directory
357+
caseroot: CASEROOT where the xmlchange command is located
358+
359+
Raises:
360+
Exception: If the subprocess for the xmlchange command fails,
361+
an exception is raised with the error message.
362+
"""
363+
364+
unlock_file("env_build.xml", caseroot)
365+
366+
command = [
367+
"./xmlchange",
368+
"--append",
369+
# The libraries must be on the same line because CIME flags an
370+
# error for multi-character arguments preceded by a single dash
371+
f"CAM_LINKED_LIBS=-L{os.path.join(musica_install_path, 'lib64')} -lmusica-fortran -lmusica -lyaml-cpp"
372+
]
373+
try:
374+
subprocess.run(command, cwd=caseroot, stdout=subprocess.PIPE,
375+
stderr=subprocess.PIPE, text=True, check=False)
376+
except subprocess.CalledProcessError as e:
377+
raise subprocess.CalledProcessError(e.returncode, e.cmd, "The subprocess \
378+
for xmlchange to set the MUSICA library path failed.") from e
379+
except FileNotFoundError as e:
380+
raise FileNotFoundError("The 'xmlchange' command was not found.") from e
381+
except OSError as e:
382+
raise OSError("An error occurred while executing the 'xmlchange' command.") from e
383+
384+
lock_file("env_build.xml", caseroot)
385+
386+
###############################################################################
387+
def _clone_and_checkout(repo_url: str, tag_name: str, clone_dest: str) -> None:
388+
###############################################################################
389+
"""
390+
Clones a Git repository from the URL and checks out a specific branch.
391+
392+
Args:
393+
repo_url: URL of the Git repository to clone
394+
tag_name: tag name to check out
395+
clone_dest: destination where the repository will be cloned
396+
397+
Raises:
398+
Exception: If the `git clone` or `git checkout` commands fail,
399+
an exception is raised with the error message.
400+
"""
401+
repo_name = repo_url.split("/")[-1].replace(".git", "")
402+
repo_path = os.path.join(clone_dest, repo_name)
403+
404+
if os.path.exists(repo_path):
405+
shutil.rmtree(repo_path)
406+
407+
try:
408+
subprocess.run(["git", "clone", repo_url, repo_path],
409+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False)
410+
except subprocess.CalledProcessError as e:
411+
raise subprocess.CalledProcessError(e.returncode, e.cmd, f"The subprocess \
412+
for git to clone the repository {repo_url} failed.") from e
413+
except FileNotFoundError as e:
414+
raise FileNotFoundError("The 'git' command was not found.") from e
415+
except OSError as e:
416+
raise OSError("An error occurred while executing the 'git' command.") from e
417+
418+
try:
419+
subprocess.run(["git", "-C", repo_path, "checkout", tag_name],
420+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False)
421+
except subprocess.CalledProcessError as e:
422+
raise subprocess.CalledProcessError(e.returncode, e.cmd, f"The subprocess \
423+
for git to checkout the branch {tag_name} failed.") from e
424+
except FileNotFoundError as e:
425+
raise FileNotFoundError("The 'git' command was not found.") from e
426+
except OSError as e:
427+
raise OSError("An error occurred while executing the 'git' command.") from e
428+
229429
###############################################################################
230430

231431
if __name__ == "__main__":

cime_config/buildnml

+1-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def buildnml(case, caseroot, compname):
166166
gen_indent = 3
167167

168168
#Generate model code and meta-data:
169-
config.generate_cam_src(gen_indent)
169+
_ = config.generate_cam_src(gen_indent)
170170

171171
#----------------------------------------------------------------
172172
# Create namelist attribute dictionary (to set namelist defaults):

cime_config/cam_autogen.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ def generate_physics_suites(build_cache, preproc_defs, host_name,
452452
# Find the SDFs specified for this model build
453453
sdfs = []
454454
scheme_files = []
455+
scheme_names = set()
455456
xml_files = {} # key is scheme, value is xml file path
456457
for sdf in phys_suites_str.split(';'):
457458
sdf_path = _find_file(f"suite_{sdf}.xml", suite_search)
@@ -467,6 +468,8 @@ def generate_physics_suites(build_cache, preproc_defs, host_name,
467468
# Given an SDF, find all the schemes it calls
468469
_, suite = read_xml_file(sdf_path)
469470
sdf_schemes = _find_schemes_in_sdf(suite)
471+
#Add schemes to set of all scheme names:
472+
scheme_names.update(sdf_schemes)
470473
# For each scheme, find its metadata file
471474
for scheme in sdf_schemes:
472475
if scheme in all_scheme_files:
@@ -655,7 +658,7 @@ def generate_physics_suites(build_cache, preproc_defs, host_name,
655658
# End if
656659

657660
return [physics_blddir, genccpp_dir], do_gen_ccpp, cap_output_file, \
658-
xml_files.values(), capgen_db
661+
xml_files.values(), capgen_db, scheme_names
659662

660663
###############################################################################
661664
def generate_init_routines(build_cache, bldroot, force_ccpp, force_init,

cime_config/cam_config.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -868,7 +868,7 @@ def generate_cam_src(self, gen_fort_indent):
868868
self.__atm_root, self.__bldroot,
869869
reg_dir, reg_files, source_mods_dir,
870870
force_ccpp)
871-
phys_dirs, force_init, _, nml_fils, capgen_db = retvals
871+
phys_dirs, force_init, _, nml_fils, capgen_db, scheme_names = retvals
872872

873873
# Add namelist definition files to dictionary:
874874
for nml_fil in nml_fils:
@@ -899,6 +899,9 @@ def generate_cam_src(self, gen_fort_indent):
899899
#--------------------------------------------------------------
900900
build_cache.write()
901901

902+
#Return the set of all scheme names present in the SDFs:
903+
return scheme_names
904+
902905
#++++++++++++++++++++++++
903906

904907
def ccpp_phys_set(self, cam_nml_attr_dict, phys_nl_pg_dict):

docker/Dockerfile

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ RUN dnf -y update \
1919
vim \
2020
&& dnf clean all
2121

22-
RUN ln -s $(which python3) /usr/bin/python && \
22+
RUN rm -f /usr/bin/python && \
23+
ln -s $(which python3) /usr/bin/python && \
2324
pip install --upgrade pip && \
2425
pip install --upgrade setuptools
2526

0 commit comments

Comments
 (0)