Skip to content

Commit e9c9317

Browse files
authored
Fix flexpart floating point exception, refactor grib2 loader (#28)
* add new grib loader, add and wgrib2 lib user to Dockerfile * fix dlon dlat in OUTGRID file * fixed test files and refactor helper funcions
1 parent fc3ae50 commit e9c9317

10 files changed

Lines changed: 268 additions & 125 deletions

File tree

Dockerfile

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
11
FROM --platform=linux/amd64 ubuntu:18.04
22
LABEL maintainer Synkevych Roman "synkevych.roman@gmail.com"
33

4-
# Set user and group as in your working machine
5-
ARG user=flexpart
6-
ARG group=flexpart
7-
ARG uid=1002
8-
ARG gid=1002
9-
RUN groupadd -g ${gid} ${group}
10-
RUN useradd -u ${uid} -g ${group} -s /bin/sh -m ${user}
11-
124
# Install all dependencies
135
RUN apt-get update && apt-get install -y \
146
openssh-server software-properties-common build-essential \
157
make gcc g++ zlib1g-dev python3 python3-pip \
16-
gfortran autoconf libtool automake bison cmake \
17-
libeccodes0 libeccodes-dev libnetcdff-dev
8+
gfortran autoconf libtool automake bison libssl-dev\
9+
libeccodes0 libeccodes-dev libnetcdff-dev vim
1810

1911
RUN add-apt-repository 'deb http://security.ubuntu.com/ubuntu xenial-security main'\
2012
&& apt-get update \
@@ -24,11 +16,43 @@ RUN add-apt-repository 'deb http://security.ubuntu.com/ubuntu xenial-security ma
2416
# Enable MPI
2517
RUN apt-get -y install openmpi-bin libopenmpi-dev \
2618
&& rm -rf /var/lib/apt/lists/*
19+
# Set the environment variables for the C and Fortran compilers
20+
ENV CC=gcc
21+
ENV FC=gfortran
22+
23+
# Install CMake 3.20.0
24+
RUN wget https://github.com/Kitware/CMake/releases/download/v3.20.0/cmake-3.20.0.tar.gz\
25+
&& tar -zxvf cmake-3.20.0.tar.gz \
26+
&& cd cmake-3.20.0 \
27+
&& ./bootstrap \
28+
&& make \
29+
&& make install \
30+
&& cd .. \
31+
&& rm -rf cmake-3.20.0.tar.gz cmake-3.20.0
32+
33+
# Install wgrib2
34+
RUN wget https://www.ftp.cpc.ncep.noaa.gov/wd51we/wgrib2/wgrib2.tgz \
35+
&& tar -zxvf wgrib2.tgz \
36+
&& cd grib2 \
37+
&& make \
38+
&& cp wgrib2/wgrib2 /usr/local/bin/ \
39+
&& cd .. \
40+
&& rm -rf wgrib2.tgz grib2
41+
42+
# Set user and group as in your working machine
43+
ARG user=flexpart
44+
ARG group=root
45+
ARG uid=1002
46+
ARG gid=1002
47+
# RUN groupadd -g ${gid} ${group}
48+
RUN useradd -u ${uid} -g ${group} -s /bin/sh -m ${user}
2749

2850
#
2951
# Download, modify and compile FLEXPART 10
3052
#
53+
3154
COPY flexpart_v10.4/ flexpart_v10.4
55+
RUN chown -R flexpart /flexpart_v10.4/
3256

3357
RUN cd flexpart_v10.4/src \
3458
&& cp makefile makefile_local \
@@ -43,6 +67,7 @@ RUN cd flexpart_v10.4/src \
4367
#
4468

4569
COPY simflex_v1/ /simflex_v1
70+
RUN chown -R flexpart /simflex_v1/
4671

4772
RUN cd /simflex_v1/src \
4873
&& gfortran -c m_parse.for m_simflex.for \

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ Open Multi-Processing ([OpenMP](http://www.openmp.org/))
137137
- [ ] Use command `docker run -d -it --entrypoint="/bin/bash" -v "$PWD":/data/ -v /home/flexpart/series/:/series/ simflex:final` to connect to the container without calculations(for example test purpose)
138138
- [ ] And then use `docker exec -it simflex /bin/bash` to connect to the running container
139139

140-
Flexpart - входние папки
140+
Command `docker inspect -f '{{.State.ExitCode}}' container_name`.
141+
If the container after calculation has *ExitCode 0* then everything is ok, if *ExitCode 1* then something went wrong while calculation, container should be restarted, if *ExitCode 2* then something went wrong with user files and user should be informed about it.
141142

142143
## Resources
143144

calculation/download_prognose.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
# 2 month equal to 44 Gb / calc speed and time needed to downloads this data
1010

1111
# URL example: https://www.ncei.noaa.gov/data/global-forecast-system/access/grid-003-1.0-degree/analysis/202104/20210417/gfs_3_20210417_1800_006.grb2
12-
# should be changed to: https://data.rda.ucar.edu/ds083.2/grib2/2022/2022.04/fnl_20220419_12_00.grib2'
1312

1413
HHMMSS = ['030000', '060000', '090000', '120000',
1514
'150000', '180000', '210000', '000000']

calculation/dw_gb.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import os
2+
import requests
3+
from datetime import datetime, timedelta
4+
import subprocess
5+
from helper import parse_messages, write_to_file, create_folder
6+
from settings import GRIB2_FILES_PATH
7+
8+
# move DATA_FOLDER to a settings file and import it here
9+
10+
start_loading_time = datetime.now()
11+
available_template_header = """XXXXXX EMPTY LINES XXXXXXXXX
12+
XXXXXX EMPTY LINES XXXXXXXX
13+
YYYYMMDD HHMMSS name of the file(up to 80 characters)
14+
"""
15+
16+
17+
def parse_available_file(date, hour, file_name):
18+
available_template_body = f"""{date.strftime('%Y%m%d')} {hour}0000 {file_name} ON DISC\n"""
19+
write_to_file('', 'AVAILABLE', available_template_body, 'a')
20+
21+
22+
def compress_grib_record(file_path, new_file_path):
23+
for start, end in [(1, 6), (9, 14), (75, 76), (83, 84), (95, 97), (101, 102), (104, 107), (111, 112), (114, 117), (121, 122), (124, 127), (131, 132), (134, 137), (141, 142), (144, 147), (151, 156), (163, 167), (171, 177), (181, 186), (193, 198), (202, 213), (217, 224), (226, 240), (242, 256), (258, 272), (274, 288), (290, 304), (306, 320), (322, 336), (338, 347), (349, 352), (354, 368), (370, 379), (381, 384), (386, 395), (397, 400), (402, 411), (413, 416), (418, 432), (434, 443), (445, 448), (450, 459), (461, 464), (466, 480), (482, 491), (493, 496), (498, 507), (509, 512), (514, 523), (525, 529), (531, 540), (542, 544), (546, 558), (561, 565), (567, 568), (570, 571), (573, 574), (577, 578), (580, 593), (598, 607), (613, 676), (679, 683), (685, 696)]:
24+
command = f"wgrib2 {file_path} -for_n {start}:{end} -grib >(cat >> {new_file_path}) > /dev/null 2>&1"
25+
process = subprocess.Popen(command, shell=True, executable="/bin/bash")
26+
process.communicate()
27+
parse_messages(f"File {file_path} updated successfully.\n")
28+
29+
30+
def prepare_file(current_date, hour, file_name, file_compressed=True):
31+
full_file_path = os.path.join(GRIB2_FILES_PATH, file_name)
32+
new_name = file_name.split(".")[0] + "_.grib2"
33+
full_new_path = os.path.join(GRIB2_FILES_PATH, new_name)
34+
35+
# check is file need to be compressed and compressed it if needed
36+
if current_date > datetime(2021, 3, 21, 6):
37+
if not os.path.isfile(full_new_path):
38+
compress_grib_record(
39+
full_file_path, full_new_path) if not file_compressed else None
40+
41+
parse_available_file(current_date, hour, new_name)
42+
else:
43+
parse_available_file(current_date, hour, file_name)
44+
45+
46+
def download_data(start, end):
47+
parse_messages('Started loading grid data.')
48+
49+
if type(start) is not datetime:
50+
parse_messages("grib_error: Start Date is incorrect", True)
51+
elif type(end) is not datetime:
52+
parse_messages("grib_error: End Date is incorrect", True)
53+
elif (end - start).days > 61:
54+
parse_messages("grib_error: Difference between start and end dates should be less than 60 days", True)
55+
else:
56+
# dates should ends with hours that divides to 3
57+
if start.hour % 6 != 0:
58+
start_date = start - timedelta(hours = start.hour % 6)
59+
else:
60+
start_date = start - timedelta(hours = 6)
61+
62+
if end.hour % 6 != 0:
63+
end_date = end + timedelta(hours = (end.hour % 6) + 6)
64+
else:
65+
end_date = end + timedelta(hours = 6)
66+
67+
# remove from start_date and end_date minutes and seconds
68+
start_date = start_date.replace(minute=0, second=0, microsecond=0)
69+
end_date = end_date.replace(minute=0, second=0, microsecond=0)
70+
71+
write_to_file('', 'AVAILABLE', available_template_header)
72+
73+
# URL example: https://data.rda.ucar.edu/ds083.2/grib2/2022/2022.04/fnl_20220419_12_00.grib2
74+
url_template = "https://data.rda.ucar.edu/ds083.2/grib2/{year}/{year}.{month}/fnl_{date}_{hour}_00.grib2"
75+
# Create a directory to store the downloaded files
76+
create_folder(GRIB2_FILES_PATH)
77+
78+
# Loop through the dates between start_date and end_date (inclusive)
79+
current_date = start_date
80+
while current_date <= end_date:
81+
# create correct hour value with leading zero
82+
hour = str(current_date.hour).zfill(2)
83+
# Generate the URL for the current date and hour
84+
url = url_template.format(year=current_date.year, month=current_date.strftime("%m"),
85+
date=current_date.strftime("%Y%m%d"), hour=hour)
86+
87+
# Generate the output file name
88+
file_name = f"fnl_{current_date.strftime('%Y%m%d')}_{hour}_00.grib2"
89+
file_path = os.path.join(GRIB2_FILES_PATH, file_name)
90+
new_name = file_name.split(".")[0] + "_.grib2"
91+
new_path = os.path.join(GRIB2_FILES_PATH, new_name)
92+
# check if file already exists or already compressed
93+
if current_date > datetime(2021, 3, 21, 6) and os.path.isfile(new_path):
94+
parse_messages(
95+
f"File {new_path} already exists. Skipping download.")
96+
parse_available_file(current_date, hour, new_name)
97+
current_date += timedelta(hours=6)
98+
continue
99+
elif os.path.isfile(file_path):
100+
parse_messages(
101+
f"File {file_path} already exists. Skipping download.")
102+
prepare_file(current_date, hour, file_name, False)
103+
current_date += timedelta(hours=6)
104+
continue
105+
else:
106+
# Download the grib data and parse AVAILABLE file
107+
response = None
108+
try:
109+
parse_messages(f"Trying to download the file {file_path} ")
110+
response = requests.get(url)
111+
except Exception as ex:
112+
parse_messages(
113+
f"grib_error: Problem with the connection to the URL {url}", True)
114+
else:
115+
if response.status_code == 200:
116+
file = open(file_path, "wb")
117+
try:
118+
file.write(response.content)
119+
parse_messages(f"File {file_path} downloaded successfully.\n")
120+
prepare_file(current_date, hour, file_name, False)
121+
finally:
122+
file.close()
123+
else:
124+
parse_messages(f"grib_error: Failed to download file {file_name} from {url}", True)
125+
126+
# Move to the next date
127+
current_date += timedelta(hours=6)
128+
parse_messages(
129+
f"Finished loading grid data and filling AVAILABLE file, it took {datetime.now()-start_loading_time}.\n")

calculation/helper.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
from subprocess import run
55
import logging
66
import sys
7+
from settings import LOGS_PATH, COMPLETE_CALCULATION_FILE
78

8-
logging.basicConfig(filename="/data/calculations_server.log", level=logging.INFO,
9+
logging.basicConfig(filename=LOGS_PATH, level=logging.INFO,
910
format="%(asctime)s %(message)s")
1011

1112
def parse_messages(msg, exit=False):
13+
run(f"""echo \"{msg}\" """, shell=True)
1214

1315
if exit:
1416
logging.error(msg)
@@ -18,23 +20,21 @@ def parse_messages(msg, exit=False):
1820
# file.write(msg)
1921
# file.close()
2022
# create a file to indicate that the calculation is done
21-
open('/data/done.txt', 'a').close()
23+
open(COMPLETE_CALCULATION_FILE, 'a').close()
2224

2325
sys.exit(msg)
2426
else:
2527
logging.info(msg)
26-
rc = run("""echo \"{message}\" """.format(message=msg), shell=True)
2728

2829

29-
def write_to_file(full_path, file_name, contents, mode='w'):
30+
def write_to_file(path, file_name, contents, mode='w'):
31+
file_path = os.path.join(path, file_name)
3032

31-
file = open(full_path + file_name, mode)
33+
file = open(file_path, mode)
3234
file.write(contents)
3335
file.close()
3436
logging.info(f'Parsing {file_name} file compleated.')
3537

3638

37-
def create_folder(directory=None):
38-
39-
if not os.path.exists(directory):
40-
os.makedirs(directory)
39+
def create_folder(dir_name=None):
40+
os.makedirs(dir_name, exist_ok=True)

0 commit comments

Comments
 (0)