├── tests ├── requirements.txt └── test_code.py ├── .DS_Store ├── tox.ini ├── ai_models_fuxi ├── __init__.py └── model.py ├── README.md ├── .github └── workflows │ └── python-publish.yml ├── setup.py ├── .gitignore └── LICENSE /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | # Empty for now 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpys/ai-models-fuxi/HEAD/.DS_Store -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [isort] 2 | profile = black 3 | 4 | [flake8] 5 | ; ignore = E226,E302,E41 6 | max-line-length = 120 7 | ; exclude = tests/* 8 | ; See https://black.readthedocs.io/en/stable/the_black_code_style.html 9 | exclude = 10 | dev/* 11 | experiments 12 | ?.py 13 | extend-ignore = E203 14 | -------------------------------------------------------------------------------- /ai_models_fuxi/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2023 European Centre for Medium-Range Weather Forecasts. 2 | # This software is licensed under the terms of the Apache Licence Version 2.0 3 | # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 4 | # In applying this licence, ECMWF does not waive the privileges and immunities 5 | # granted to it by virtue of its status as an intergovernmental organisation 6 | # nor does it submit to any jurisdiction. 7 | 8 | __version__ = "0.1.0" 9 | -------------------------------------------------------------------------------- /tests/test_code.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2023 European Centre for Medium-Range Weather Forecasts. 2 | # This software is licensed under the terms of the Apache Licence Version 2.0 3 | # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 4 | # In applying this licence, ECMWF does not waive the privileges and immunities 5 | # granted to it by virtue of its status as an intergovernmental organisation 6 | # nor does it submit to any jurisdiction. 7 | 8 | 9 | def test_code(): 10 | pass # Empty for now 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ai-models-fuxi 2 | 3 | `ai-models-fuxi` is an [ai-models](https://github.com/ecmwf-lab/ai-models) plugin to run [Fudan's FuXi](https://github.com/tpys/FuXi.git). 4 | 5 | FuXi: A cascade machine learning forecasting system for 15-day global weather forecast,arXiv preprint: 2306.12873, 2022. https://arxiv.org/pdf/2306.12873.pdf 6 | 7 | FuXi was created by Lei Chen, Xiaohui Zhong, Feng Zhang, Yuan Cheng, Yinghui Xu, Yuan Qi, Hao Li. It is released by Fudan University. 8 | 9 | The model weights are made available under the terms of the BY-NC-SA 4.0 license. 10 | The commercial use of these models is forbidden. Please download the pre-trained models from Google drive: https://drive.google.com/drive/folders/1CXxv-s4DdIHVxSCGsDLMn38n8Shk3fRs 11 | 12 | 13 | For use other input data eg: ERA5 and GFS see for further details. 14 | 15 | 16 | ### Installation 17 | 18 | To install the package, run: 19 | 20 | ```bash 21 | pip install ai-models-fuxi 22 | ``` 23 | 24 | This will install the package and its dependencies, in particular the ONNX runtime. The installation script will attempt to guess which runtime to install. You can force a given runtime by specifying the the `ONNXRUNTIME` variable, e.g.: 25 | 26 | ```bash 27 | ONNXRUNTIME=onnxruntime-gpu pip install ai-models-fuxi 28 | ``` 29 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | 8 | push: {} 9 | 10 | release: 11 | types: [created] 12 | 13 | jobs: 14 | quality: 15 | name: Code QA 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - run: pip install black flake8 isort 20 | - run: black --version 21 | - run: isort --version 22 | - run: flake8 --version 23 | - run: isort --check . 24 | - run: black --check . 25 | - run: flake8 . 26 | 27 | checks: 28 | if: ${{ github.event_name == 'release' }} 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | platform: ["ubuntu-latest", "macos-latest"] 34 | python-version: ["3.10"] 35 | 36 | name: Python ${{ matrix.python-version }} on ${{ matrix.platform }} 37 | runs-on: ${{ matrix.platform }} 38 | needs: quality 39 | 40 | steps: 41 | - uses: actions/checkout@v2 42 | 43 | - uses: actions/setup-python@v2 44 | with: 45 | python-version: ${{ matrix.python-version }} 46 | 47 | - name: Install 48 | run: | 49 | pip install pytest 50 | pip install -e . 51 | pip install -r tests/requirements.txt 52 | pip freeze 53 | 54 | - name: Tests 55 | run: pytest 56 | 57 | deploy: 58 | 59 | if: ${{ github.event_name == 'release' }} 60 | runs-on: ubuntu-latest 61 | needs: checks 62 | 63 | steps: 64 | - uses: actions/checkout@v2 65 | 66 | - name: Set up Python 67 | uses: actions/setup-python@v2 68 | with: 69 | python-version: '3.x' 70 | 71 | - name: Check that tag version matches code version 72 | run: | 73 | tag=${GITHUB_REF#refs/tags/} 74 | version=$(python setup.py --version) 75 | echo 'tag='$tag 76 | echo "version file="$version 77 | test "$tag" == "$version" 78 | 79 | - name: Install dependencies 80 | run: | 81 | python -m pip install --upgrade pip 82 | pip install setuptools wheel twine 83 | - name: Build and publish 84 | env: 85 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 86 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 87 | run: | 88 | python setup.py sdist 89 | twine upload dist/* 90 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # (C) Copyright 2023 ECMWF. 3 | # 4 | # This software is licensed under the terms of the Apache Licence Version 2.0 5 | # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 6 | # In applying this licence, ECMWF does not waive the privileges and immunities 7 | # granted to it by virtue of its status as an intergovernmental organisation 8 | # nor does it submit to any jurisdiction. 9 | # 10 | 11 | 12 | import io 13 | import os 14 | import platform 15 | import subprocess 16 | import sys 17 | 18 | import setuptools 19 | 20 | 21 | def read(fname): 22 | file_path = os.path.join(os.path.dirname(__file__), fname) 23 | return io.open(file_path, encoding="utf-8").read() 24 | 25 | 26 | version = None 27 | for line in read("ai_models_fuxi/__init__.py").split("\n"): 28 | if line.startswith("__version__"): 29 | version = line.split("=")[-1].strip()[1:-1] 30 | 31 | assert version 32 | 33 | 34 | def check_gpus(): 35 | try: 36 | n = 0 37 | for line in subprocess.check_output( 38 | ["nvidia-smi", "-L"], 39 | text=True, 40 | ).split("\n"): 41 | if line.startswith("GPU"): 42 | n += 1 43 | return n 44 | except (subprocess.CalledProcessError, FileNotFoundError): 45 | return 0 46 | 47 | 48 | assert check_gpus() > 0, "FuXi model only support gpu inference" 49 | onnxruntime = "onnxruntime-gpu" 50 | 51 | setuptools.setup( 52 | name="ai-models-fuxi", 53 | python_requires="<3.11", # For now, does not support Python 3.11 54 | version=version, 55 | description="An ai-models plugin to run FuXi", 56 | long_description=read("README.md"), 57 | long_description_content_type="text/markdown", 58 | author="European Centre for Medium-Range Weather Forecasts (ECMWF)", 59 | author_email="software.support@ecmwf.int", 60 | license="Apache License Version 2.0", 61 | url="https://github.com/tpys/ai-models-fuxi", 62 | packages=setuptools.find_packages(), 63 | include_package_data=True, 64 | setup_requires=["GPUtil"], 65 | install_requires=[ 66 | "ai-models", 67 | "onnx", 68 | os.environ.get("ONNXRUNTIME", onnxruntime), 69 | ], 70 | zip_safe=True, 71 | keywords="tool", 72 | entry_points={ 73 | "ai_models.model": [ 74 | "fuxi = ai_models_fuxi.model:FuXi", 75 | ] 76 | }, 77 | classifiers=[ 78 | "Development Status :: 3 - Alpha", 79 | "Intended Audience :: Developers", 80 | "License :: OSI Approved :: Apache Software License", 81 | "Programming Language :: Python :: 3", 82 | "Programming Language :: Python :: 3.9", 83 | "Programming Language :: Python :: 3.10", 84 | "Programming Language :: Python :: 3.11", 85 | "Programming Language :: Python :: Implementation :: CPython", 86 | "Programming Language :: Python :: Implementation :: PyPy", 87 | "Operating System :: OS Independent", 88 | ], 89 | ) 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /ai_models_fuxi/model.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2023 European Centre for Medium-Range Weather Forecasts. 2 | # This software is licensed under the terms of the Apache Licence Version 2.0 3 | # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 4 | # In applying this licence, ECMWF does not waive the privileges and immunities 5 | # granted to it by virtue of its status as an intergovernmental organisation 6 | # nor does it submit to any jurisdiction. 7 | 8 | 9 | import logging 10 | import os 11 | from collections import defaultdict 12 | from ai_models.model import Model 13 | 14 | import numpy as np 15 | import xarray as xr 16 | import pandas as pd 17 | 18 | LOG = logging.getLogger(__name__) 19 | 20 | 21 | def time_encoding(init_time, total_step, freq=6): 22 | init_time = np.array([init_time]) 23 | tembs = [] 24 | for i in range(total_step): 25 | hours = np.array([pd.Timedelta(hours=t*freq) for t in [i-1, i, i+1]]) 26 | times = init_time[:, None] + hours[None] 27 | times = [pd.Period(t, 'H') for t in times.reshape(-1)] 28 | times = [(p.day_of_year/366, p.hour/24) for p in times] 29 | temb = np.array(times, dtype=np.float32) 30 | temb = np.concatenate([np.sin(temb), np.cos(temb)], axis=-1) 31 | temb = temb.reshape(1, -1) 32 | tembs.append(temb) 33 | return np.stack(tembs) 34 | 35 | 36 | 37 | class FuXi(Model): 38 | expver = "fuxi" 39 | use_an = False 40 | debug_fx = False 41 | 42 | download_url = ( 43 | "https://get.ecmwf.int/repository/test-data/ai-models/fuxi/{file}" 44 | ) 45 | download_files = [ 46 | "short", 47 | "short.onnx", 48 | "meidum", 49 | "meidum.onnx", 50 | "long", 51 | "long.onnx", 52 | ] 53 | 54 | area = [90, 0, -90, 360] 55 | grid = [0.25, 0.25] 56 | param_sfc = ["2t", "10u", "10v", "msl", "tp"] 57 | param_level_pl = ( 58 | ["z", "t", "u", "v", "r"], 59 | [50, 100, 150, 200, 250, 300, 400, 500, 600, 700, 850, 925, 1000], 60 | ) 61 | 62 | def __init__(self, num_threads=1, **kwargs): 63 | super().__init__(**kwargs) 64 | self.num_threads = num_threads 65 | self.hour_steps = 6 66 | self.lagged = [-6, 0] 67 | self.stages = ["short", "medium", "long"] 68 | 69 | self.ordering = [ 70 | f"{param}{level}" 71 | for param in self.param_level_pl[0] 72 | for level in self.param_level_pl[1] 73 | ] + self.param_sfc 74 | 75 | 76 | def patch_retrieve_request(self, r): 77 | if r.get("class", "od") != "od": 78 | return 79 | 80 | if r.get("type", "an") not in ("an", "fc"): 81 | return 82 | 83 | if r.get("stream", "oper") not in ("oper", "scda"): 84 | return 85 | 86 | if self.use_an: 87 | r["type"] = "an" 88 | else: 89 | r["type"] = "fc" 90 | 91 | time = r.get("time", 12) 92 | r["stream"] = {0: "oper", 6: "scda", 12: "oper", 18: "scda"}[time] 93 | 94 | 95 | def load_model(self): 96 | import onnxruntime as ort 97 | ort.set_default_logger_severity(3) 98 | options = ort.SessionOptions() 99 | options.enable_cpu_mem_arena = False 100 | options.enable_mem_pattern = False 101 | options.enable_mem_reuse = False 102 | options.intra_op_num_threads = self.num_threads 103 | models = {} 104 | for stage in self.stages: 105 | model_file = os.path.join(self.assets, f"{stage}.onnx") 106 | os.stat(model_file) 107 | with self.timer(f"Loading {model_file}"): 108 | model = ort.InferenceSession( 109 | model_file, 110 | sess_options=options, 111 | providers=self.providers, 112 | ) 113 | models[stage] = model 114 | return models 115 | 116 | def get_init_time(self): 117 | init_time = self.all_fields.order_by(valid_datetime="descending")[0].datetime() 118 | init_time = pd.to_datetime(init_time) 119 | return init_time 120 | 121 | def create_input(self, init_time): 122 | hist_time = init_time - pd.Timedelta(hours=6) 123 | valid_time = [hist_time, init_time] 124 | valid_time_str = [t.strftime('%Y-%m-%dT%H:%M:%S') for t in valid_time] 125 | 126 | param_sfc = self.param_sfc 127 | param_pl, level = self.param_level_pl 128 | fields_pl = self.fields_pl 129 | fields_sfc = self.fields_sfc 130 | 131 | lat = fields_sfc[0].metadata("distinctLatitudes") 132 | lon = fields_sfc[0].metadata("distinctLongitudes") 133 | 134 | fields_pl = fields_pl.sel(valid_datetime=valid_time_str, param=param_pl, level=level) 135 | fields_pl = fields_pl.order_by(param=param_pl, valid_datetime=valid_time_str, level=level) 136 | 137 | pl = defaultdict(list) 138 | for field in fields_pl: 139 | pl[field.metadata("param")].append(field) 140 | 141 | fields_sfc = fields_sfc.sel(valid_datetime=valid_time_str, param=param_sfc) 142 | fields_sfc = fields_sfc.order_by(param=param_sfc, valid_datetime=valid_time_str) 143 | 144 | sfc = defaultdict(list) 145 | for field in fields_sfc: 146 | sfc[field.metadata("param")].append(field) 147 | 148 | input = [] 149 | for param, fields in pl.items(): 150 | data = np.stack( 151 | [field.to_numpy(dtype=np.float32) for field in fields] 152 | ).reshape(-1, len(level), len(lat), len(lon)) 153 | input.append(data) 154 | info = (f"Name: {param}, shape: {data.shape}, range: {data.min():.3f} ~ {data.max():.3f}") 155 | LOG.info(info) 156 | 157 | for param, fields in sfc.items(): 158 | data = np.stack( 159 | [field.to_numpy(dtype=np.float32) for field in fields] 160 | ).reshape(-1, 1, len(lat), len(lon)) 161 | input.append(data) 162 | info = (f"Name: {param}, shape: {data.shape}, range: {data.min():.3f} ~ {data.max():.3f}") 163 | LOG.info(info) 164 | 165 | input = np.concatenate(input, axis=1) 166 | 167 | if self.debug_fx: 168 | self.input_xr = xr.DataArray( 169 | data=input, 170 | coords=dict( 171 | time=valid_time, 172 | channel=self.ordering, 173 | lat=lat, 174 | lon=lon, 175 | ), 176 | ) 177 | save_name = valid_time[-1].strftime("%Y%m%d%H.nc") 178 | self.input_xr.to_netcdf(save_name) 179 | 180 | self.template_pl = fields_pl.sel(valid_datetime=valid_time_str[-1]) 181 | self.template_sfc = fields_sfc.sel(valid_datetime=valid_time_str[-1]) 182 | return input[None] 183 | 184 | 185 | def run(self): 186 | total_step = self.lead_time // self.hour_steps 187 | 188 | models = self.load_model() 189 | init_time = self.get_init_time() 190 | tembs = time_encoding(init_time, total_step) 191 | input = self.create_input(init_time) 192 | 193 | with self.stepper(6) as stepper: 194 | for i in range(total_step): 195 | step = (i + 1) * self.hour_steps 196 | stage = self.stages[min(2, i//20)] 197 | 198 | new_input, = models[stage].run( 199 | None, {'input': input, 'temb': tembs[i]} 200 | ) 201 | 202 | pl_chans = len(self.ordering) - len(self.param_sfc) 203 | pl_data = new_input[0, -1, :pl_chans] 204 | sfc_data = new_input[0, -1, pl_chans:] 205 | 206 | for data, f in zip(pl_data, self.template_pl): 207 | self.write(data, template=f, step=step) 208 | 209 | for data, f in zip(sfc_data, self.template_sfc): 210 | self.write(data, template=f, step=step) 211 | 212 | if self.debug_fx: 213 | ds = xr.DataArray( 214 | data=new_input[:, -1], 215 | coords=dict( 216 | step=[step], 217 | channel=self.input_xr.channel, 218 | lat=self.input_xr.lat, 219 | lon=self.input_xr.lon, 220 | ), 221 | ) 222 | save_dir = init_time.strftime("%Y%m%d%H") 223 | os.makedirs(save_dir, exist_ok=True) 224 | ds.to_netcdf(os.path.join(save_dir, f"{step:03d}.nc")) 225 | 226 | stepper(i, step) 227 | input = new_input 228 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------