Time Series Forecasting
Transformers
Safetensors
fela_grid_renewable
feature-extraction
fela
fourier-neural-operator
fno
cpu
on-device
energy-forecasting
solar-power
wind-power
probabilistic-forecasting
quantile-regression
custom_code
Instructions to use lowdown-labs/fela-power-grid with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use lowdown-labs/fela-power-grid with Transformers:
# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("lowdown-labs/fela-power-grid", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Commit ·
3476d8e
0
Parent(s):
initial commit
Browse files- .gitattributes +1 -0
- .gitignore +6 -0
- LICENSE +474 -0
- README.md +340 -0
- config.json +38 -0
- configuration_grid.py +39 -0
- model.safetensors +3 -0
- modeling.py +185 -0
- modeling_grid.py +56 -0
- quickstart/README.md +45 -0
- quickstart/requirements.txt +4 -0
- quickstart/run.py +41 -0
- solar.safetensors +3 -0
- space/README.md +42 -0
- space/app.py +95 -0
- space/requirements.txt +3 -0
- streaming/manifest.json +28 -0
- streaming/model_fp16.safetensors +3 -0
- streaming/solar_fp16.safetensors +3 -0
- streaming/wind_fp16.safetensors +3 -0
- train.py +283 -0
- verify.py +44 -0
- wind.safetensors +3 -0
.gitattributes
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.ruff_cache/
|
| 2 |
+
.venv/
|
| 3 |
+
__pycache__/
|
| 4 |
+
*.swp
|
| 5 |
+
*.kate-swp
|
| 6 |
+
*~
|
LICENSE
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Lowdown Labs Lovely License 1.0 (LLLL-1.0)
|
| 2 |
+
|
| 3 |
+
Everything in this repository (the model weights, the configuration, and the code) is
|
| 4 |
+
released by Lowdown Labs under two licenses that apply at the same time. To use this work
|
| 5 |
+
you must comply with BOTH of them. Where a term in one is stricter than the other, the
|
| 6 |
+
stricter term controls. "Lowdown Labs Lovely License 1.0" is a convenience name for this
|
| 7 |
+
exact pair; it is not a new legal instrument.
|
| 8 |
+
|
| 9 |
+
SPDX-License-Identifier: CC-BY-NC-4.0 AND LicenseRef-Hippocratic-3.0
|
| 10 |
+
|
| 11 |
+
Commercial licensing. The grant below is non-commercial only. Commercial licenses are
|
| 12 |
+
sold separately by Lowdown Labs on a per-customer basis. To use this work, its weights, or
|
| 13 |
+
its outputs for any commercial purpose, contact Lowdown Labs to purchase a commercial
|
| 14 |
+
license. A commercial license does not remove the Hippocratic ethical-use obligations in
|
| 15 |
+
Part 2; those apply to commercial licensees as well.
|
| 16 |
+
|
| 17 |
+
==============================================================================
|
| 18 |
+
Part 1 of 2. Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)
|
| 19 |
+
==============================================================================
|
| 20 |
+
|
| 21 |
+
You may share and adapt this work for non-commercial purposes, with attribution to
|
| 22 |
+
Lowdown Labs. Commercial use is not granted under this license.
|
| 23 |
+
|
| 24 |
+
Full legal text: https://creativecommons.org/licenses/by-nc/4.0/legalcode
|
| 25 |
+
Plain-language summary: https://creativecommons.org/licenses/by-nc/4.0/
|
| 26 |
+
SPDX-License-Identifier: CC-BY-NC-4.0
|
| 27 |
+
|
| 28 |
+
==============================================================================
|
| 29 |
+
Part 2 of 2. The Hippocratic License 3.0 (ethical use)
|
| 30 |
+
==============================================================================
|
| 31 |
+
|
| 32 |
+
Module set enabled: bds, cl, eco, extr, ffd, law, media, mil, my, soc, sup, sv, usta.
|
| 33 |
+
Canonical build: https://firstdonoharm.dev/build/?modules=bds,cl,eco,extr,ffd,law,media,mil,my,soc,sup,sv,usta
|
| 34 |
+
|
| 35 |
+
The verbatim official Hippocratic License 3.0 text for exactly this module set follows,
|
| 36 |
+
between the markers.
|
| 37 |
+
|
| 38 |
+
--------------------- BEGIN OFFICIAL HIPPOCRATIC LICENSE 3.0 TEXT ---------------------
|
| 39 |
+
|
| 40 |
+
HIPPOCRATIC LICENSE
|
| 41 |
+
|
| 42 |
+
Version 3.0, October 2021
|
| 43 |
+
|
| 44 |
+
https://firstdonoharm.dev/version/3/0/bds-cl-eco-extr-ffd-law-media-mil-my-soc-sup-sv-usta.txt
|
| 45 |
+
|
| 46 |
+
TERMS AND CONDITIONS
|
| 47 |
+
|
| 48 |
+
TERMS AND CONDITIONS FOR USE, COPY, MODIFICATION, PREPARATION OF DERIVATIVE
|
| 49 |
+
WORK, REPRODUCTION, AND DISTRIBUTION:
|
| 50 |
+
|
| 51 |
+
1. DEFINITIONS:
|
| 52 |
+
|
| 53 |
+
This section defines certain terms used throughout this license agreement.
|
| 54 |
+
|
| 55 |
+
1.1. “License” means the terms and conditions, as stated herein, for use, copy,
|
| 56 |
+
modification, preparation of derivative work, reproduction, and distribution of
|
| 57 |
+
Software (as defined below).
|
| 58 |
+
|
| 59 |
+
1.2. “Licensor” means the copyright and/or patent owner or entity authorized by
|
| 60 |
+
the copyright and/or patent owner that is granting the License.
|
| 61 |
+
|
| 62 |
+
1.3. “Licensee” means the individual or entity exercising permissions granted by
|
| 63 |
+
this License, including the use, copy, modification, preparation of derivative
|
| 64 |
+
work, reproduction, and distribution of Software (as defined below).
|
| 65 |
+
|
| 66 |
+
1.4. “Software” means any copyrighted work, including but not limited to
|
| 67 |
+
software code, authored by Licensor and made available under this License.
|
| 68 |
+
|
| 69 |
+
1.5. “Supply Chain” means the sequence of processes involved in the production
|
| 70 |
+
and/or distribution of a commodity, good, or service offered by the Licensee.
|
| 71 |
+
|
| 72 |
+
1.6. “Supply Chain Impacted Party” or “Supply Chain Impacted Parties” means any
|
| 73 |
+
person(s) directly impacted by any of Licensee’s Supply Chain, including the
|
| 74 |
+
practices of all persons or entities within the Supply Chain prior to a good or
|
| 75 |
+
service reaching the Licensee.
|
| 76 |
+
|
| 77 |
+
1.7. “Duty of Care” is defined by its use in tort law, delict law, and/or
|
| 78 |
+
similar bodies of law closely related to tort and/or delict law, including
|
| 79 |
+
without limitation, a requirement to act with the watchfulness, attention,
|
| 80 |
+
caution, and prudence that a reasonable person in the same or similar
|
| 81 |
+
circumstances would use towards any Supply Chain Impacted Party.
|
| 82 |
+
|
| 83 |
+
1.8. “Worker” is defined to include any and all permanent, temporary, and agency
|
| 84 |
+
workers, as well as piece-rate, salaried, hourly paid, legal young (minors),
|
| 85 |
+
part-time, night, and migrant workers.
|
| 86 |
+
|
| 87 |
+
2. INTELLECTUAL PROPERTY GRANTS:
|
| 88 |
+
|
| 89 |
+
This section identifies intellectual property rights granted to a Licensee.
|
| 90 |
+
|
| 91 |
+
2.1. Grant of Copyright License: Subject to the terms and conditions of this
|
| 92 |
+
License, Licensor hereby grants to Licensee a worldwide, non-exclusive,
|
| 93 |
+
no-charge, royalty-free copyright license to use, copy, modify, prepare
|
| 94 |
+
derivative work, reproduce, or distribute the Software, Licensor authored
|
| 95 |
+
modified software, or other work derived from the Software.
|
| 96 |
+
|
| 97 |
+
2.2. Grant of Patent License: Subject to the terms and conditions of this
|
| 98 |
+
License, Licensor hereby grants Licensee a worldwide, non-exclusive, no-charge,
|
| 99 |
+
royalty-free patent license to make, have made, use, offer to sell, sell,
|
| 100 |
+
import, and otherwise transfer Software.
|
| 101 |
+
|
| 102 |
+
3. ETHICAL STANDARDS:
|
| 103 |
+
|
| 104 |
+
This section lists conditions the Licensee must comply with in order to have
|
| 105 |
+
rights under this License.
|
| 106 |
+
|
| 107 |
+
The rights granted to the Licensee by this License are expressly made subject to
|
| 108 |
+
the Licensee’s ongoing compliance with the following conditions:
|
| 109 |
+
|
| 110 |
+
* 3.1. The Licensee SHALL NOT, whether directly or indirectly, through agents
|
| 111 |
+
or assigns:
|
| 112 |
+
|
| 113 |
+
* 3.1.1. Infringe upon any person’s right to life or security of person,
|
| 114 |
+
engage in extrajudicial killings, or commit murder, without lawful cause
|
| 115 |
+
(See Article 3, United Nations Universal Declaration of Human Rights;
|
| 116 |
+
Article 6, International Covenant on Civil and Political Rights)
|
| 117 |
+
|
| 118 |
+
* 3.1.2. Hold any person in slavery, servitude, or forced labor (See Article
|
| 119 |
+
4, United Nations Universal Declaration of Human Rights; Article 8,
|
| 120 |
+
International Covenant on Civil and Political Rights);
|
| 121 |
+
|
| 122 |
+
* 3.1.3. Contribute to the institution of slavery, slave trading, forced
|
| 123 |
+
labor, or unlawful child labor (See Article 4, United Nations Universal
|
| 124 |
+
Declaration of Human Rights; Article 8, International Covenant on Civil and
|
| 125 |
+
Political Rights);
|
| 126 |
+
|
| 127 |
+
* 3.1.4. Torture or subject any person to cruel, inhumane, or degrading
|
| 128 |
+
treatment or punishment (See Article 5, United Nations Universal
|
| 129 |
+
Declaration of Human Rights; Article 7, International Covenant on Civil and
|
| 130 |
+
Political Rights);
|
| 131 |
+
|
| 132 |
+
* 3.1.5. Discriminate on the basis of sex, gender, sexual orientation, race,
|
| 133 |
+
ethnicity, nationality, religion, caste, age, medical disability or
|
| 134 |
+
impairment, and/or any other like circumstances (See Article 7, United
|
| 135 |
+
Nations Universal Declaration of Human Rights; Article 2, International
|
| 136 |
+
Covenant on Economic, Social and Cultural Rights; Article 26, International
|
| 137 |
+
Covenant on Civil and Political Rights);
|
| 138 |
+
|
| 139 |
+
* 3.1.6. Prevent any person from exercising his/her/their right to seek an
|
| 140 |
+
effective remedy by a competent court or national tribunal (including
|
| 141 |
+
domestic judicial systems, international courts, arbitration bodies, and
|
| 142 |
+
other adjudicating bodies) for actions violating the fundamental rights
|
| 143 |
+
granted to him/her/them by applicable constitutions, applicable laws, or by
|
| 144 |
+
this License (See Article 8, United Nations Universal Declaration of Human
|
| 145 |
+
Rights; Articles 9 and 14, International Covenant on Civil and Political
|
| 146 |
+
Rights);
|
| 147 |
+
|
| 148 |
+
* 3.1.7. Subject any person to arbitrary arrest, detention, or exile (See
|
| 149 |
+
Article 9, United Nations Universal Declaration of Human Rights; Article 9,
|
| 150 |
+
International Covenant on Civil and Political Rights);
|
| 151 |
+
|
| 152 |
+
* 3.1.8. Subject any person to arbitrary interference with a person’s
|
| 153 |
+
privacy, family, home, or correspondence without the express written
|
| 154 |
+
consent of the person (See Article 12, United Nations Universal Declaration
|
| 155 |
+
of Human Rights; Article 17, International Covenant on Civil and Political
|
| 156 |
+
Rights);
|
| 157 |
+
|
| 158 |
+
* 3.1.9. Arbitrarily deprive any person of his/her/their property (See
|
| 159 |
+
Article 17, United Nations Universal Declaration of Human Rights);
|
| 160 |
+
|
| 161 |
+
* 3.1.10. Forcibly remove indigenous peoples from their lands or territories
|
| 162 |
+
or take any action with the aim or effect of dispossessing indigenous
|
| 163 |
+
peoples from their lands, territories, or resources, including without
|
| 164 |
+
limitation the intellectual property or traditional knowledge of indigenous
|
| 165 |
+
peoples, without the free, prior, and informed consent of indigenous
|
| 166 |
+
peoples concerned (See Articles 8 and 10, United Nations Declaration on the
|
| 167 |
+
Rights of Indigenous Peoples);
|
| 168 |
+
* 3.1.11. Fossil Fuel Divestment: Be an individual or entity, or a
|
| 169 |
+
representative, agent, affiliate, successor, attorney, or assign of an
|
| 170 |
+
individual or entity, on the FFI Solutions Carbon Underground 200 list
|
| 171 |
+
[https://www.ffisolutions.com/research-analytics-index-solutions/research-screening/the-carbon-underground-200/?cn-reloaded=1];
|
| 172 |
+
|
| 173 |
+
* 3.1.12. Ecocide: Commit ecocide:
|
| 174 |
+
|
| 175 |
+
* 3.1.12.1. For the purpose of this section, “ecocide” means unlawful or
|
| 176 |
+
wanton acts committed with knowledge that there is a substantial
|
| 177 |
+
likelihood of severe and either widespread or long-term damage to the
|
| 178 |
+
environment being caused by those acts;
|
| 179 |
+
|
| 180 |
+
* 3.1.12.2. For the purpose of further defining ecocide and the terms
|
| 181 |
+
contained in the previous paragraph:
|
| 182 |
+
|
| 183 |
+
* 3.1.12.2.1. “Wanton” means with reckless disregard for damage which
|
| 184 |
+
would be clearly excessive in relation to the social and economic
|
| 185 |
+
benefits anticipated;
|
| 186 |
+
|
| 187 |
+
* 3.1.12.2.2. “Severe” means damage which involves very serious adverse
|
| 188 |
+
changes, disruption, or harm to any element of the environment,
|
| 189 |
+
including grave impacts on human life or natural, cultural, or
|
| 190 |
+
economic resources;
|
| 191 |
+
|
| 192 |
+
* 3.1.12.2.3. “Widespread” means damage which extends beyond a limited
|
| 193 |
+
geographic area, crosses state boundaries, or is suffered by an entire
|
| 194 |
+
ecosystem or species or a large number of human beings;
|
| 195 |
+
|
| 196 |
+
* 3.1.12.2.4. “Long-term” means damage which is irreversible or which
|
| 197 |
+
cannot be redressed through natural recovery within a reasonable
|
| 198 |
+
period of time; and
|
| 199 |
+
|
| 200 |
+
* 3.1.12.2.5. “Environment” means the earth, its biosphere, cryosphere,
|
| 201 |
+
lithosphere, hydrosphere, and atmosphere, as well as outer space
|
| 202 |
+
|
| 203 |
+
(See Section II, Independent Expert Panel for the Legal Definition of
|
| 204 |
+
Ecocide, Stop Ecocide Foundation and the Promise Institute for Human
|
| 205 |
+
Rights at UCLA School of Law, June 2021);
|
| 206 |
+
|
| 207 |
+
* 3.1.13. Extractive Industries: Be an individual or entity, or a
|
| 208 |
+
representative, agent, affiliate, successor, attorney, or assign of an
|
| 209 |
+
individual or entity, that engages in fossil fuel or mineral exploration,
|
| 210 |
+
extraction, development, or sale;
|
| 211 |
+
|
| 212 |
+
* 3.1.14. Boycott / Divestment / Sanctions: Be an individual or entity, or a
|
| 213 |
+
representative, agent, affiliate, successor, attorney, or assign of an
|
| 214 |
+
individual or entity, identified by the Boycott, Divestment, Sanctions
|
| 215 |
+
(“BDS”) movement on its website (https://bdsmovement.net/
|
| 216 |
+
[https://bdsmovement.net/] and
|
| 217 |
+
https://bdsmovement.net/get-involved/what-to-boycott
|
| 218 |
+
[https://bdsmovement.net/get-involved/what-to-boycott]) as a target for
|
| 219 |
+
boycott;
|
| 220 |
+
|
| 221 |
+
* 3.1.15. Myanmar: Be an individual or entity that:
|
| 222 |
+
|
| 223 |
+
* 3.1.15.1. engages in any commercial transactions with the
|
| 224 |
+
Myanmar/Burmese military junta; or
|
| 225 |
+
|
| 226 |
+
* 3.1.15.2. is a representative, agent, affiliate, successor, attorney, or
|
| 227 |
+
assign of the Myanmar/Burmese government;
|
| 228 |
+
|
| 229 |
+
* 3.1.16. US Tariff Act: Be an individual or entity:
|
| 230 |
+
|
| 231 |
+
* 3.1.16.1. which U.S. Customs and Border Protection (CBP) has currently
|
| 232 |
+
issued a Withhold Release Order (WRO) or finding against based on
|
| 233 |
+
reasonable suspicion of forced labor; or
|
| 234 |
+
|
| 235 |
+
* 3.1.16.2. that is a representative, agent, affiliate, successor,
|
| 236 |
+
attorney, or assign of an individual or entity that does business with
|
| 237 |
+
an individual or entity which currently has a WRO or finding from CBP
|
| 238 |
+
issued against it based on reasonable suspicion of forced labor;
|
| 239 |
+
|
| 240 |
+
* 3.1.17. Mass Surveillance: Be a government agency or multinational
|
| 241 |
+
corporation, or a representative, agent, affiliate, successor, attorney,
|
| 242 |
+
or assign of a government or multinational corporation, which participates
|
| 243 |
+
in mass surveillance programs;
|
| 244 |
+
|
| 245 |
+
* 3.1.18. Military Activities: Be an entity or a representative, agent,
|
| 246 |
+
affiliate, successor, attorney, or assign of an entity which conducts
|
| 247 |
+
military activities;
|
| 248 |
+
|
| 249 |
+
* 3.1.19. Law Enforcement: Be an individual or entity, or a representative,
|
| 250 |
+
agent, affiliate, successor, attorney, or assign of an individual or
|
| 251 |
+
entity, that provides good or services to, or otherwise enters into any
|
| 252 |
+
commercial contracts with, any local, state, or federal law enforcement
|
| 253 |
+
agency;
|
| 254 |
+
|
| 255 |
+
* 3.1.20. Media: Be an individual or entity, or a representative, agent,
|
| 256 |
+
affiliate, successor, attorney, or assign of an individual or entity, that
|
| 257 |
+
broadcasts messages promoting killing, torture, or other forms of extreme
|
| 258 |
+
violence;
|
| 259 |
+
|
| 260 |
+
* 3.1.21. Interfere with Workers’ free exercise of the right to organize and
|
| 261 |
+
associate (See Article 20, United Nations Universal Declaration of Human
|
| 262 |
+
Rights; C087 - Freedom of Association and Protection of the Right to
|
| 263 |
+
Organise Convention, 1948 (No. 87), International Labour Organization;
|
| 264 |
+
Article 8, International Covenant on Economic, Social and Cultural Rights);
|
| 265 |
+
and
|
| 266 |
+
|
| 267 |
+
* 3.1.22. Harm the environment in a manner inconsistent with local, state,
|
| 268 |
+
national, or international law.
|
| 269 |
+
|
| 270 |
+
* 3.2. The Licensee SHALL:
|
| 271 |
+
|
| 272 |
+
* 3.2.1. Social Auditing: Only use social auditing mechanisms that adhere to
|
| 273 |
+
Worker-Driven Social Responsibility Network’s Statement of Principles
|
| 274 |
+
(https://wsr-network.org/what-is-wsr/statement-of-principles/
|
| 275 |
+
[https://wsr-network.org/what-is-wsr/statement-of-principles/]) over
|
| 276 |
+
traditional social auditing mechanisms, to the extent the Licensee uses
|
| 277 |
+
any social auditing mechanisms at all;
|
| 278 |
+
|
| 279 |
+
* 3.2.2. Supply Chain: Provide clear, accessible supply chain data to the
|
| 280 |
+
public in accordance with the following conditions:
|
| 281 |
+
|
| 282 |
+
* 3.2.2.1. All data will be on Licensee’s website and/or, to the extent
|
| 283 |
+
Licensee is a representative, agent, affiliate, successor, attorney,
|
| 284 |
+
subsidiary, or assign, on Licensee’s principal’s or parent’s website or
|
| 285 |
+
some other online platform accessible to the public via an internet
|
| 286 |
+
search on a common internet search engine; and
|
| 287 |
+
|
| 288 |
+
* 3.2.2.2. Data published will include, where applicable, manufacturers,
|
| 289 |
+
top tier suppliers, subcontractors, cooperatives, component parts
|
| 290 |
+
producers, and farms;
|
| 291 |
+
|
| 292 |
+
* 3.2.3. Provide equal pay for equal work where the performance of such work
|
| 293 |
+
requires equal skill, effort, and responsibility, and which are performed
|
| 294 |
+
under similar working conditions, except where such payment is made
|
| 295 |
+
pursuant to:
|
| 296 |
+
|
| 297 |
+
* 3.2.3.1. A seniority system;
|
| 298 |
+
|
| 299 |
+
* 3.2.3.2. A merit system;
|
| 300 |
+
|
| 301 |
+
* 3.2.3.3. A system which measures earnings by quantity or quality of
|
| 302 |
+
production; or
|
| 303 |
+
|
| 304 |
+
* 3.2.3.4. A differential based on any other factor other than sex, gender,
|
| 305 |
+
sexual orientation, race, ethnicity, nationality, religion, caste, age,
|
| 306 |
+
medical disability or impairment, and/or any other like circumstances
|
| 307 |
+
(See 29 U.S.C.A. § 206(d)(1); Article 23, United Nations Universal
|
| 308 |
+
Declaration of Human Rights; Article 7, International Covenant on
|
| 309 |
+
Economic, Social and Cultural Rights; Article 26, International Covenant
|
| 310 |
+
on Civil and Political Rights); and
|
| 311 |
+
|
| 312 |
+
* 3.2.4. Allow for reasonable limitation of working hours and periodic
|
| 313 |
+
holidays with pay (See Article 24, United Nations Universal Declaration of
|
| 314 |
+
Human Rights; Article 7, International Covenant on Economic, Social and
|
| 315 |
+
Cultural Rights).
|
| 316 |
+
|
| 317 |
+
4. SUPPLY CHAIN IMPACTED PARTIES:
|
| 318 |
+
|
| 319 |
+
This section identifies additional individuals or entities that a Licensee could
|
| 320 |
+
harm as a result of violating the Ethical Standards section, the condition that
|
| 321 |
+
the Licensee must voluntarily accept a Duty of Care for those individuals or
|
| 322 |
+
entities, and the right to a private right of action that those individuals or
|
| 323 |
+
entities possess as a result of violations of the Ethical Standards section.
|
| 324 |
+
|
| 325 |
+
4.1. In addition to the above Ethical Standards, Licensee voluntarily accepts a
|
| 326 |
+
Duty of Care for Supply Chain Impacted Parties of this License, including
|
| 327 |
+
individuals and communities impacted by violations of the Ethical Standards. The
|
| 328 |
+
Duty of Care is breached when a provision within the Ethical Standards section
|
| 329 |
+
is violated by a Licensee, one of its successors or assigns, or by an individual
|
| 330 |
+
or entity that exists within the Supply Chain prior to a good or service
|
| 331 |
+
reaching the Licensee.
|
| 332 |
+
|
| 333 |
+
4.2. Breaches of the Duty of Care, as stated within this section, shall create a
|
| 334 |
+
private right of action, allowing any Supply Chain Impacted Party harmed by the
|
| 335 |
+
Licensee to take legal action against the Licensee in accordance with applicable
|
| 336 |
+
negligence laws, whether they be in tort law, delict law, and/or similar bodies
|
| 337 |
+
of law closely related to tort and/or delict law, regardless if Licensee is
|
| 338 |
+
directly responsible for the harms suffered by a Supply Chain Impacted Party.
|
| 339 |
+
Nothing in this section shall be interpreted to include acts committed by
|
| 340 |
+
individuals outside of the scope of his/her/their employment.
|
| 341 |
+
|
| 342 |
+
5. NOTICE: This section explains when a Licensee must notify others of the
|
| 343 |
+
License.
|
| 344 |
+
|
| 345 |
+
5.1. Distribution of Notice: Licensee must ensure that everyone who receives a
|
| 346 |
+
copy of or uses any part of Software from Licensee, with or without changes,
|
| 347 |
+
also receives the License and the copyright notice included with Software (and
|
| 348 |
+
if included by the Licensor, patent, trademark, and attribution notice).
|
| 349 |
+
Licensee must ensure that License is prominently displayed so that any
|
| 350 |
+
individual or entity seeking to download, copy, use, or otherwise receive any
|
| 351 |
+
part of Software from Licensee is notified of this License and its terms and
|
| 352 |
+
conditions. Licensee must cause any modified versions of the Software to carry
|
| 353 |
+
prominent notices stating that Licensee changed the Software.
|
| 354 |
+
|
| 355 |
+
5.2. Modified Software: Licensee is free to create modifications of the Software
|
| 356 |
+
and distribute only the modified portion created by Licensee, however, any
|
| 357 |
+
derivative work stemming from the Software or its code must be distributed
|
| 358 |
+
pursuant to this License, including this Notice provision.
|
| 359 |
+
|
| 360 |
+
5.3. Recipients as Licensees: Any individual or entity that uses, copies,
|
| 361 |
+
modifies, reproduces, distributes, or prepares derivative work based upon the
|
| 362 |
+
Software, all or part of the Software’s code, or a derivative work developed by
|
| 363 |
+
using the Software, including a portion of its code, is a Licensee as defined
|
| 364 |
+
above and is subject to the terms and conditions of this License.
|
| 365 |
+
|
| 366 |
+
6. REPRESENTATIONS AND WARRANTIES:
|
| 367 |
+
|
| 368 |
+
6.1. Disclaimer of Warranty: TO THE FULL EXTENT ALLOWED BY LAW, THIS SOFTWARE
|
| 369 |
+
COMES “AS IS,” WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED, AND LICENSOR SHALL NOT
|
| 370 |
+
BE LIABLE TO ANY PERSON OR ENTITY FOR ANY DAMAGES OR OTHER LIABILITY ARISING
|
| 371 |
+
FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THIS LICENSE, UNDER ANY
|
| 372 |
+
LEGAL CLAIM.
|
| 373 |
+
|
| 374 |
+
6.2. Limitation of Liability: LICENSEE SHALL HOLD LICENSOR HARMLESS AGAINST ANY
|
| 375 |
+
AND ALL CLAIMS, DEBTS, DUES, LIABILITIES, LIENS, CAUSES OF ACTION, DEMANDS,
|
| 376 |
+
OBLIGATIONS, DISPUTES, DAMAGES, LOSSES, EXPENSES, ATTORNEYS’ FEES, COSTS,
|
| 377 |
+
LIABILITIES, AND ALL OTHER CLAIMS OF EVERY KIND AND NATURE WHATSOEVER, WHETHER
|
| 378 |
+
KNOWN OR UNKNOWN, ANTICIPATED OR UNANTICIPATED, FORESEEN OR UNFORESEEN, ACCRUED
|
| 379 |
+
OR UNACCRUED, DISCLOSED OR UNDISCLOSED, ARISING OUT OF OR RELATING TO LICENSEE’S
|
| 380 |
+
USE OF THE SOFTWARE. NOTHING IN THIS SECTION SHOULD BE INTERPRETED TO REQUIRE
|
| 381 |
+
LICENSEE TO INDEMNIFY LICENSOR, NOR REQUIRE LICENSOR TO INDEMNIFY LICENSEE.
|
| 382 |
+
|
| 383 |
+
7. TERMINATION
|
| 384 |
+
|
| 385 |
+
7.1. Violations of Ethical Standards or Breaching Duty of Care: If Licensee
|
| 386 |
+
violates the Ethical Standards section or Licensee, or any other person or
|
| 387 |
+
entity within the Supply Chain prior to a good or service reaching the Licensee,
|
| 388 |
+
breaches its Duty of Care to Supply Chain Impacted Parties, Licensee must remedy
|
| 389 |
+
the violation or harm caused by Licensee within 30 days of being notified of the
|
| 390 |
+
violation or harm. If Licensee fails to remedy the violation or harm within 30
|
| 391 |
+
days, all rights in the Software granted to Licensee by License will be null and
|
| 392 |
+
void as between Licensor and Licensee.
|
| 393 |
+
|
| 394 |
+
7.2. Failure of Notice: If any person or entity notifies Licensee in writing
|
| 395 |
+
that Licensee has not complied with the Notice section of this License, Licensee
|
| 396 |
+
can keep this License by taking all practical steps to comply within 30 days
|
| 397 |
+
after the notice of noncompliance. If Licensee does not do so, Licensee’s
|
| 398 |
+
License (and all rights licensed hereunder) will end immediately.
|
| 399 |
+
|
| 400 |
+
7.3. Judicial Findings: In the event Licensee is found by a civil, criminal,
|
| 401 |
+
administrative, or other court of competent jurisdiction, or some other
|
| 402 |
+
adjudicating body with legal authority, to have committed actions which are in
|
| 403 |
+
violation of the Ethical Standards or Supply Chain Impacted Party sections of
|
| 404 |
+
this License, all rights granted to Licensee by this License will terminate
|
| 405 |
+
immediately.
|
| 406 |
+
|
| 407 |
+
7.4. Patent Litigation: If Licensee institutes patent litigation against any
|
| 408 |
+
entity (including a cross-claim or counterclaim in a suit) alleging that the
|
| 409 |
+
Software, all or part of the Software’s code, or a derivative work developed
|
| 410 |
+
using the Software, including a portion of its code, constitutes direct or
|
| 411 |
+
contributory patent infringement, then any patent license, along with all other
|
| 412 |
+
rights, granted to Licensee under this License will terminate as of the date
|
| 413 |
+
such litigation is filed.
|
| 414 |
+
|
| 415 |
+
7.5. Additional Remedies: Termination of the License by failing to remedy harms
|
| 416 |
+
in no way prevents Licensor or Supply Chain Impacted Party from seeking
|
| 417 |
+
appropriate remedies at law or in equity.
|
| 418 |
+
|
| 419 |
+
8. MISCELLANEOUS:
|
| 420 |
+
|
| 421 |
+
8.1. Conditions: Sections 3, 4.1, 5.1, 5.2, 7.1, 7.2, 7.3, and 7.4 are
|
| 422 |
+
conditions of the rights granted to Licensee in the License.
|
| 423 |
+
|
| 424 |
+
8.2. Equitable Relief: Licensor and any Supply Chain Impacted Party shall be
|
| 425 |
+
entitled to equitable relief, including injunctive relief or specific
|
| 426 |
+
performance of the terms hereof, in addition to any other remedy to which they
|
| 427 |
+
are entitled at law or in equity.
|
| 428 |
+
|
| 429 |
+
8.3. Copyleft: Modified software, source code, or other derivative work must be
|
| 430 |
+
licensed, in its entirety, under the exact same conditions as this License.
|
| 431 |
+
|
| 432 |
+
8.4. Severability: If any term or provision of this License is determined to be
|
| 433 |
+
invalid, illegal, or unenforceable by a court of competent jurisdiction, any
|
| 434 |
+
such determination of invalidity, illegality, or unenforceability shall not
|
| 435 |
+
affect any other term or provision of this License or invalidate or render
|
| 436 |
+
unenforceable such term or provision in any other jurisdiction. If the
|
| 437 |
+
determination of invalidity, illegality, or unenforceability by a court of
|
| 438 |
+
competent jurisdiction pertains to the terms or provisions contained in the
|
| 439 |
+
Ethical Standards section of this License, all rights in the Software granted to
|
| 440 |
+
Licensee shall be deemed null and void as between Licensor and Licensee.
|
| 441 |
+
|
| 442 |
+
8.5. Section Titles: Section titles are solely written for organizational
|
| 443 |
+
purposes and should not be used to interpret the language within each section.
|
| 444 |
+
|
| 445 |
+
8.6. Citations: Citations are solely written to provide context for the source
|
| 446 |
+
of the provisions in the Ethical Standards.
|
| 447 |
+
|
| 448 |
+
8.7. Section Summaries: Some sections have a brief italicized description which
|
| 449 |
+
is provided for the sole purpose of briefly describing the section and should
|
| 450 |
+
not be used to interpret the terms of the License.
|
| 451 |
+
|
| 452 |
+
8.8. Entire License: This is the entire License between the Licensor and
|
| 453 |
+
Licensee with respect to the claims released herein and that the consideration
|
| 454 |
+
stated herein is the only consideration or compensation to be paid or exchanged
|
| 455 |
+
between them for this License. This License cannot be modified or amended except
|
| 456 |
+
in a writing signed by Licensor and Licensee.
|
| 457 |
+
|
| 458 |
+
8.9. Successors and Assigns: This License shall be binding upon and inure to the
|
| 459 |
+
benefit of the Licensor’s and Licensee’s respective heirs, successors, and
|
| 460 |
+
assigns.
|
| 461 |
+
---------------------- END OFFICIAL HIPPOCRATIC LICENSE 3.0 TEXT ----------------------
|
| 462 |
+
|
| 463 |
+
==============================================================================
|
| 464 |
+
Attribution, commercial use, and warranty
|
| 465 |
+
==============================================================================
|
| 466 |
+
|
| 467 |
+
Attribution: cite this work as described in the model card (README.md), section
|
| 468 |
+
"How to cite". Attribution to Lowdown Labs is required under CC BY-NC 4.0.
|
| 469 |
+
|
| 470 |
+
Commercial use: CC BY-NC 4.0 does not grant commercial rights. Commercial licenses are
|
| 471 |
+
sold by Lowdown Labs; contact Lowdown Labs to purchase one.
|
| 472 |
+
|
| 473 |
+
No warranty: this work is provided as is, without warranty of any kind. See the model card
|
| 474 |
+
for the intended use, the evaluated conditions, and the known limitations.
|
README.md
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: other
|
| 3 |
+
license_name: lowdown-labs-lovely-license-1.0
|
| 4 |
+
license_link: LICENSE
|
| 5 |
+
tags:
|
| 6 |
+
- fela
|
| 7 |
+
- fourier-neural-operator
|
| 8 |
+
- fno
|
| 9 |
+
- cpu
|
| 10 |
+
- on-device
|
| 11 |
+
- energy-forecasting
|
| 12 |
+
- solar-power
|
| 13 |
+
- wind-power
|
| 14 |
+
- probabilistic-forecasting
|
| 15 |
+
- quantile-regression
|
| 16 |
+
library_name: transformers
|
| 17 |
+
pipeline_tag: time-series-forecasting
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
# DISCLAIMER
|
| 21 |
+
|
| 22 |
+
This model is a research preview. GEFCom2014 is competition data with no stated reuse or
|
| 23 |
+
commercial use grant (citation only), so respect that before any commercial use. Lowdown Labs
|
| 24 |
+
has put together this model in the interest of advancing public science.
|
| 25 |
+
|
| 26 |
+
# FELA Power Grid: on station probabilistic solar and wind power forecasting
|
| 27 |
+
|
| 28 |
+
This model forecasts how much power a solar farm or a wind farm will produce in the
|
| 29 |
+
coming hours, and it gives a full range of outcomes (not just a single guess) so a grid
|
| 30 |
+
operator can plan for the good case and the bad case. It is small enough to fit the
|
| 31 |
+
envelope of a cheap microcontroller right at the station, with no cloud and no network
|
| 32 |
+
connection, running there through an ONNX or TFLite export.
|
| 33 |
+
|
| 34 |
+
# What goes in, what comes out
|
| 35 |
+
|
| 36 |
+
- Input: a short window of weather forecast numbers for the site (numerical weather
|
| 37 |
+
prediction, or NWP, covariates such as forecast irradiance for solar or forecast wind
|
| 38 |
+
speed and direction for wind). Solar input shape is (1, 6, 20): 6 time steps, 20
|
| 39 |
+
weather features. Wind input shape is (1, 12, 15): 12 time steps, 15 weather features.
|
| 40 |
+
- Output: 99 numbers per forecast hour, the 1 percent through 99 percent quantiles of
|
| 41 |
+
power output, normalized to the site's rated capacity (0 to 1). A quantile is a "what
|
| 42 |
+
if" level: the 10 percent quantile (P10) is a low estimate that power should exceed
|
| 43 |
+
90 percent of the time, the 90 percent quantile (P90) is a high estimate. The gap
|
| 44 |
+
between P10 and P90 is the uncertainty band the operator plans against.
|
| 45 |
+
- This lets a grid or plant operator schedule reserves, bid into a market, and manage
|
| 46 |
+
ramps with a calibrated sense of risk, using a device that sits on the station and
|
| 47 |
+
keeps working when the network is down.
|
| 48 |
+
|
| 49 |
+
# Why we built it this way
|
| 50 |
+
|
| 51 |
+
The model is a dual path Fourier Neural Operator, or FNO. The main path mixes information in the
|
| 52 |
+
frequency domain: a fast Fourier transform, a learned filter on the frequencies, then a transform
|
| 53 |
+
back. Weather and power move on daily and seasonal cycles, which is cheap to capture this way.
|
| 54 |
+
Alongside it runs a small local mixer for the short range detail.
|
| 55 |
+
|
| 56 |
+
Everything is kept deliberately small: 136,780 parameters for solar, 311,554 for wind. Quantized to
|
| 57 |
+
8 bit integers each track is under a megabyte, and a single hourly forecast takes under a
|
| 58 |
+
millisecond on one CPU core (measured on an x86 server CPU). That is small enough to sit on a
|
| 59 |
+
microcontroller at the station, running through the ONNX or TFLite export. Because it runs there
|
| 60 |
+
offline, the site's operational data never leaves the premises.
|
| 61 |
+
|
| 62 |
+
# Performance
|
| 63 |
+
|
| 64 |
+
Speed and footprint, measured on CPU (AMD EPYC 9555, batch size 1, median of 20 runs).
|
| 65 |
+
|
| 66 |
+
| Track | Parameters | fp32 size | int8 size | Latency, 1 core |
|
| 67 |
+
|---|---|---|---|---|
|
| 68 |
+
| Solar, input (1, 6, 20) | 136,780 | 0.74 MB | 504 KB | 0.386 ms |
|
| 69 |
+
| Wind, input (1, 12, 15) | 311,554 | 1.91 MB | 1472 KB | 0.375 ms |
|
| 70 |
+
|
| 71 |
+
The int8 weights are the on device deploy size. The int8 pinball loss is essentially
|
| 72 |
+
unchanged from fp32 (see Accuracy), so quantization is effectively lossless here.
|
| 73 |
+
|
| 74 |
+
# Accuracy
|
| 75 |
+
|
| 76 |
+
The benchmark is GEFCom2014, the standard Global Energy Forecasting Competition dataset
|
| 77 |
+
(Hong et al. 2016). The protocol is the final task (Task 15): train on all data before the
|
| 78 |
+
held out test month and forecast that month from weather inputs only, with no test period
|
| 79 |
+
power used (so there is no leakage).
|
| 80 |
+
|
| 81 |
+
Solar is 3 zones (test month June 2014), wind is 10
|
| 82 |
+
zones (test month December 2013). The metric is pinball loss averaged over the 1 to 99
|
| 83 |
+
percent quantiles, with power normalized to site capacity, exactly as in the competition.
|
| 84 |
+
Lower pinball loss is better. "Skill" is the percent reduction in pinball loss against a
|
| 85 |
+
named reference forecast.
|
| 86 |
+
|
| 87 |
+
| Benchmark | Metric | This model | Baseline (named) | Source |
|
| 88 |
+
|---|---|---|---|---|
|
| 89 |
+
| GEFCom2014 solar | pinball (norm.) | 0.01308 (int8 0.01328) | competition benchmark 0.0285 | measured (ours) |
|
| 90 |
+
| GEFCom2014 solar | skill vs competition benchmark | +54.1 percent | competition benchmark | measured (ours) |
|
| 91 |
+
| GEFCom2014 solar | skill vs diurnal persistence | +32.1 percent | diurnal persistence | measured (ours) |
|
| 92 |
+
| GEFCom2014 solar | pinball (norm.) | 0.01308 | our LightGBM quantile baseline 0.01232 | measured (ours) |
|
| 93 |
+
| GEFCom2014 solar | pinball (norm.) | 0.01308 | published LSTM/quantile NN 0.0143 | published |
|
| 94 |
+
| GEFCom2014 wind | pinball (norm.) | 0.04690 (int8 0.04692) | competition benchmark 0.0792 | measured (ours) |
|
| 95 |
+
| GEFCom2014 wind | skill vs competition benchmark | +40.8 percent | competition benchmark | measured (ours) |
|
| 96 |
+
| GEFCom2014 wind | skill vs diurnal persistence | +47.5 percent | diurnal persistence | measured (ours) |
|
| 97 |
+
| GEFCom2014 wind | pinball (norm.) | 0.04690 | our LightGBM quantile baseline 0.04547 | measured (ours) |
|
| 98 |
+
| GEFCom2014 wind | pinball (norm.) | 0.04690 | published GAN / normalizing flow / VAE / DDPM | published |
|
| 99 |
+
|
| 100 |
+
The model wins the official GEFCom2014 benchmark on both tracks, and it beats
|
| 101 |
+
the competition benchmark by wide margins (skill +54.1 percent on solar, +40.8 percent on wind).
|
| 102 |
+
What it is not is a new raw pinball record.
|
| 103 |
+
|
| 104 |
+
On the identical pipeline it ties our own LightGBM
|
| 105 |
+
gradient boosted baseline (solar 0.01308 vs 0.01232, wind 0.04690 vs 0.04547). It beats a
|
| 106 |
+
published LSTM/quantile NN on solar and a published GAN on wind, sits level with a published
|
| 107 |
+
normalizing flow, and lands a few percent behind the best published diffusion model (VAE/DDPM)
|
| 108 |
+
on wind.
|
| 109 |
+
|
| 110 |
+
So - the accuracy is competitive but with given resources, not chart topping. The real edge is where it delivers
|
| 111 |
+
that accuracy: 136,780 and 311,554 parameters, under a megabyte in int8, sub millisecond on a
|
| 112 |
+
CPU, running on station with the network gapped - which we feel is a great domain adaptation for our methodologies.
|
| 113 |
+
|
| 114 |
+
# How to run it
|
| 115 |
+
|
| 116 |
+
See `quickstart/` for a runnable example. The model loads in a few lines with the bundled
|
| 117 |
+
`modeling.py` plus `config.json`, from the safe `safetensors` weight file (no pickle):
|
| 118 |
+
|
| 119 |
+
```python
|
| 120 |
+
from huggingface_hub import hf_hub_download
|
| 121 |
+
import modeling # bundled in this repo
|
| 122 |
+
|
| 123 |
+
path = hf_hub_download("lowdown-labs/fela-power-grid", "solar.safetensors")
|
| 124 |
+
model = modeling.load_model(path, track="solar")
|
| 125 |
+
|
| 126 |
+
# Preprocess a raw NWP window (shape (6, 20) for solar), then forecast.
|
| 127 |
+
x = modeling.preprocess_nwp(raw_window, track="solar") # validates shape, standardizes
|
| 128 |
+
import torch
|
| 129 |
+
with torch.no_grad():
|
| 130 |
+
quantiles = model(x) # (1, 99): the P1..P99 power quantiles for the center forecast hour
|
| 131 |
+
|
| 132 |
+
# A P10 to P90 band for the center forecast hour:
|
| 133 |
+
p10 = quantiles[0, 9].item()
|
| 134 |
+
p90 = quantiles[0, 89].item()
|
| 135 |
+
print("Center hour P10..P90 (fraction of capacity):", p10, p90)
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
The `modeling.preprocess_nwp` helper standardizes the weather window and validates its
|
| 139 |
+
shape (it fails clearly on the wrong shape or units). For an interactive playground, see
|
| 140 |
+
the Hugging Face Space linked in this repo.
|
| 141 |
+
|
| 142 |
+
## Formats
|
| 143 |
+
|
| 144 |
+
This model is CPU native: no GPU is required to run it, in any format. The fp32 and int8
|
| 145 |
+
formats run on a plain CPU.
|
| 146 |
+
|
| 147 |
+
- fp32: reference and CPU.
|
| 148 |
+
- int8: on device deployment format (AVX512-VNNI on x86, NEON dot product on ARM). The
|
| 149 |
+
int8 pinball loss is essentially unchanged (solar 0.01328 vs 0.01308, wind 0.04692 vs
|
| 150 |
+
0.04690), so on device quantization is effectively lossless here.
|
| 151 |
+
- bf16: an optional format for server or GPU inference, not required and not the on device
|
| 152 |
+
format. Most commodity ARM and microcontroller CPUs lack native bf16, so use fp32 or int8
|
| 153 |
+
there. You never need a GPU; bf16 is only a convenience when one happens to be present.
|
| 154 |
+
|
| 155 |
+
## Serving
|
| 156 |
+
|
| 157 |
+
For serving at scale, use the separate CPU native FELA server (https://github.com/Lowdown-Labs/fela_server). It
|
| 158 |
+
runs this model on CPU, with no GPU required. The quickstart in this repo is the minimal
|
| 159 |
+
single process path; the FELA server is the production serving path.
|
| 160 |
+
|
| 161 |
+
# Training data
|
| 162 |
+
|
| 163 |
+
- GEFCom2014 (Global Energy Forecasting Competition 2014), public competition data
|
| 164 |
+
released with the competition. Used for both training and the held out evaluation, under
|
| 165 |
+
the GEFCom2014 Task 15 protocol described above. Solar and wind tracks. Citation: Hong,
|
| 166 |
+
Pinson, Fan, Zareipour, Troccoli, Hyndman (2016), "Probabilistic energy forecasting:
|
| 167 |
+
Global Energy Forecasting Competition 2014 and beyond," International Journal of
|
| 168 |
+
Forecasting 32(3). The dataset is the public competition release; check the competition
|
| 169 |
+
terms for the exact redistribution conditions before rehosting it.
|
| 170 |
+
|
| 171 |
+
No proprietary or customer data was used. The model takes only numerical weather
|
| 172 |
+
prediction covariates as input.
|
| 173 |
+
|
| 174 |
+
## Training data, splits and licensing
|
| 175 |
+
|
| 176 |
+
The training and held out evaluation splits are defined in `train.py` in this repo. A `--smoke`
|
| 177 |
+
flag rebuilds the split, asserts the audited held out window count per track, and exits before
|
| 178 |
+
training.
|
| 179 |
+
|
| 180 |
+
- Dataset: GEFCom2014 (Global Energy Forecasting Competition 2014), solar and wind tracks.
|
| 181 |
+
Version: the Task 15 (final task) public release, as distributed with the paper. The solar
|
| 182 |
+
track has 3 zones with 12 NWP predictors; the wind track has 10 zones with 4 NWP predictors.
|
| 183 |
+
- Source: the data was released as the appendix of the GEFCom2014 paper and mirrored by the
|
| 184 |
+
competition General Chair at http://blog.drhongtao.com/2017/03/gefcom2014-load-forecasting-data.html
|
| 185 |
+
(and via ScienceDirect, DOI 10.1016/j.ijforecast.2016.02.001). Citation: Hong, Pinson, Fan,
|
| 186 |
+
Zareipour, Troccoli, Hyndman (2016), International Journal of Forecasting 32(3), 896-913.
|
| 187 |
+
- Split: GEFCom2014 Task 15 protocol, a fixed calendar held out month, not a random split.
|
| 188 |
+
Solar test month is June 2014 (2014-06-01 01:00 to 2014-07-01 00:00); wind test month is
|
| 189 |
+
December 2013. Training uses all prior data; the test month is forecast from NWP only (no
|
| 190 |
+
test period power, so no autoregressive leakage). The held out and train membership is the
|
| 191 |
+
`is_test` flag defined in `train.py`, and `train.py --smoke` asserts the audited held out
|
| 192 |
+
counts: solar 2154 windows over 3 zones, wind 7390 windows over 10 zones. A 6 percent tail
|
| 193 |
+
of the training rows is held out as a validation set for early stopping (deterministic tail
|
| 194 |
+
slice).
|
| 195 |
+
- License: NO license is stated for GEFCom2014. The data is competition data delivered as an
|
| 196 |
+
appendix to a copyrighted (all rights reserved) Elsevier / International Journal of
|
| 197 |
+
Forecasting article; the author distribution page states only a citation requirement, with no
|
| 198 |
+
reuse or commercial use grant. Underlying source data (e.g. ISO New England for the extended
|
| 199 |
+
load track) may carry its own upstream terms, and the distributor warns against combining the
|
| 200 |
+
datasets.
|
| 201 |
+
- Commercial verdict: UNCLEAR and UNSTATED. Competition data, no license grant, the highest risk
|
| 202 |
+
of the family. Citation alone is not a commercial use grant. For any commercial or
|
| 203 |
+
redistribution use, obtain written permission from the organizers (Tao Hong / International
|
| 204 |
+
Institute of Forecasters) and verify the upstream source terms first.
|
| 205 |
+
|
| 206 |
+
# Citations and licenses
|
| 207 |
+
|
| 208 |
+
This section consolidates the formal references and the direct links to the real license
|
| 209 |
+
text for every dataset and method used, verified from source.
|
| 210 |
+
|
| 211 |
+
## Datasets
|
| 212 |
+
|
| 213 |
+
- **GEFCom2014 (Global Energy Forecasting Competition 2014)**: solar and wind tracks, the
|
| 214 |
+
Task 15 protocol used for both training and the held out evaluation.
|
| 215 |
+
- Reference: Hong, T., Pinson, P., Fan, S., Zareipour, H., Troccoli, A., & Hyndman, R. J.
|
| 216 |
+
(2016). Probabilistic energy forecasting: Global Energy Forecasting Competition 2014 and
|
| 217 |
+
beyond. *International Journal of Forecasting*, 32(3), 896-913.
|
| 218 |
+
DOI: [10.1016/j.ijforecast.2016.02.001](https://doi.org/10.1016/j.ijforecast.2016.02.001)
|
| 219 |
+
- Data and terms: released as the appendix of the paper and mirrored by the competition General
|
| 220 |
+
Chair at
|
| 221 |
+
[blog.drhongtao.com/2017/03/gefcom2014-load-forecasting-data.html](http://blog.drhongtao.com/2017/03/gefcom2014-load-forecasting-data.html).
|
| 222 |
+
The paper itself is (c) Elsevier / International Journal of Forecasting (all rights reserved):
|
| 223 |
+
[ScienceDirect article page](https://www.sciencedirect.com/science/article/pii/S0169207016000133).
|
| 224 |
+
- **License: NO reuse or commercial use grant is stated, COMPETITION TERMS.** The distribution
|
| 225 |
+
page states only a citation requirement; the data is delivered as an appendix to a copyrighted
|
| 226 |
+
Elsevier article, and underlying source data (e.g. ISO New England for the extended load track)
|
| 227 |
+
may carry its own upstream terms. Citation is not a commercial use grant. For any commercial or
|
| 228 |
+
redistribution use, obtain written permission from the organizers (Tao Hong / International
|
| 229 |
+
Institute of Forecasters) and verify the upstream source terms first. See the fuller caveat under
|
| 230 |
+
"Training data, splits and licensing" above.
|
| 231 |
+
|
| 232 |
+
## Methods and code
|
| 233 |
+
|
| 234 |
+
- **Fourier Neural Operator (FNO)**: the sequence mixer at the core of both tracks.
|
| 235 |
+
Li, Z., Kovachki, N., Azizzadenesheli, K., Liu, B., Bhattacharya, K., Stuart, A., & Anandkumar, A.
|
| 236 |
+
(2021). Fourier Neural Operator for Parametric Partial Differential Equations. *ICLR*.
|
| 237 |
+
[arXiv:2010.08895](https://arxiv.org/abs/2010.08895)
|
| 238 |
+
- **PyTorch**: training and inference framework. Paszke, A., et al. (2019). PyTorch: An
|
| 239 |
+
Imperative Style, High-Performance Deep Learning Library. *NeurIPS*.
|
| 240 |
+
[arXiv:1912.01703](https://arxiv.org/abs/1912.01703)
|
| 241 |
+
- **LightGBM**: the reference gradient boosted quantile baseline. Ke, G., et al. (2017).
|
| 242 |
+
LightGBM: A Highly Efficient Gradient Boosting Decision Tree. *NeurIPS*.
|
| 243 |
+
[proceedings](https://papers.nips.cc/paper_files/paper/2017/hash/6449f44a102fde848669bdd9eb6b76fa-Abstract.html)
|
| 244 |
+
- **NumPy / SciPy**: used in the data preparation and evaluation pipeline; standard scientific
|
| 245 |
+
Python stack. Harris et al. (2020), [Nature 585, 357-362](https://doi.org/10.1038/s41586-020-2649-2);
|
| 246 |
+
Virtanen et al. (2020), [Nature Methods 17, 261-272](https://doi.org/10.1038/s41592-019-0686-2).
|
| 247 |
+
- **ONNX Runtime / TFLite**: the on device export and runtime path.
|
| 248 |
+
[onnxruntime.ai](https://onnxruntime.ai/),
|
| 249 |
+
[ai.google.dev/edge/litert](https://ai.google.dev/edge/litert).
|
| 250 |
+
|
| 251 |
+
This model does not use Gated Linear Attention, Gated DeltaNet, or Landmark Attention: both
|
| 252 |
+
tracks are pure FNO (see `modeling.py` and `train.py`).
|
| 253 |
+
|
| 254 |
+
# Intended use, limitations, and safety
|
| 255 |
+
|
| 256 |
+
What it is for:
|
| 257 |
+
|
| 258 |
+
- Short horizon probabilistic power forecasting at a solar or wind site, driven by a
|
| 259 |
+
numerical weather prediction feed, on station or on device.
|
| 260 |
+
|
| 261 |
+
What it is not for:
|
| 262 |
+
|
| 263 |
+
- It is not a single point guarantee of output and not a replacement for a grid operator's
|
| 264 |
+
judgment. The quantile band is a planning aid, not a control signal.
|
| 265 |
+
- It is not validated for direct, unsupervised use in a safety critical or
|
| 266 |
+
protection critical control loop. Do not wire its output into automated dispatch,
|
| 267 |
+
protection, or curtailment that affects grid stability without independent validation and
|
| 268 |
+
a human or rule based check in the loop.
|
| 269 |
+
|
| 270 |
+
Evaluated conditions and known limits:
|
| 271 |
+
|
| 272 |
+
- Evaluated only on GEFCom2014 (3 solar zones, 10 wind zones) under the Task 15 protocol.
|
| 273 |
+
Performance on other sites, climates, turbine types, or NWP feeds is not characterized
|
| 274 |
+
here and should be validated before operational use.
|
| 275 |
+
- The model is a tie with a gradient boosted (LightGBM) baseline on raw accuracy and is a
|
| 276 |
+
few percent behind the best published diffusion/VAE on wind. If raw pinball loss is the
|
| 277 |
+
only thing that matters and device size does not, those baselines are reasonable
|
| 278 |
+
alternatives. The reason to choose this model is the on station, sub megabyte,
|
| 279 |
+
sub millisecond, network gapped deployment.
|
| 280 |
+
- A separate test on SDWPF (Baidu KDD Cup 2022), a harder modern wind farm benchmark, did
|
| 281 |
+
not transfer well and is not claimed here. The genuine climate win is GEFCom2014.
|
| 282 |
+
- The quantile outputs are calibrated against the GEFCom2014 evaluation only. Verify
|
| 283 |
+
calibration on your own data before relying on the P10 to P90 band for reserve sizing.
|
| 284 |
+
|
| 285 |
+
Privacy:
|
| 286 |
+
|
| 287 |
+
- The model runs on station and offline. When deployed on device, the site's operational
|
| 288 |
+
and weather data does not leave the device, so there is no cloud round trip and no data
|
| 289 |
+
shared with Lowdown Labs or any third party.
|
| 290 |
+
|
| 291 |
+
# How to cite
|
| 292 |
+
|
| 293 |
+
Model and technical note:
|
| 294 |
+
|
| 295 |
+
```
|
| 296 |
+
@misc{lowdownlabs_grid_renewable,
|
| 297 |
+
title = {FELA Grid Renewable: on station probabilistic solar and wind power forecasting},
|
| 298 |
+
author = {Lowdown Labs},
|
| 299 |
+
year = {2026},
|
| 300 |
+
note = {Model card}
|
| 301 |
+
}
|
| 302 |
+
```
|
| 303 |
+
|
| 304 |
+
You must also cite the benchmark dataset and the core libraries:
|
| 305 |
+
|
| 306 |
+
- Hong, T., Pinson, P., Fan, S., Zareipour, H., Troccoli, A., Hyndman, R. J. (2016).
|
| 307 |
+
Probabilistic energy forecasting: Global Energy Forecasting Competition 2014 and beyond.
|
| 308 |
+
International Journal of Forecasting, 32(3), 896 to 913.
|
| 309 |
+
- Li, Z., Kovachki, N., Azizzadenesheli, K., Liu, B., Bhattacharya, K., Stuart, A.,
|
| 310 |
+
Anandkumar, A. (2021). Fourier Neural Operator for Parametric Partial Differential
|
| 311 |
+
Equations. International Conference on Learning Representations (ICLR).
|
| 312 |
+
- Paszke, A. et al. (2019). PyTorch: An Imperative Style, High-Performance Deep Learning
|
| 313 |
+
Library. NeurIPS.
|
| 314 |
+
|
| 315 |
+
# Acknowledgements and references
|
| 316 |
+
|
| 317 |
+
- GEFCom2014: Hong et al. (2016), International Journal of Forecasting 32(3), 896 to 913.
|
| 318 |
+
- Fourier Neural Operator: Li et al. (2021), ICLR. The architecture is built on the FNO.
|
| 319 |
+
- LightGBM (our reference quantile baseline): Ke, G. et al. (2017). LightGBM: A Highly
|
| 320 |
+
Efficient Gradient Boosting Decision Tree. NeurIPS.
|
| 321 |
+
- PyTorch: Paszke et al. (2019), NeurIPS.
|
| 322 |
+
|
| 323 |
+
# Model family
|
| 324 |
+
|
| 325 |
+
This is part of the FELA family from Lowdown Labs: one FNO architecture across many
|
| 326 |
+
modalities, all CPU native and subquadratic. This repo is published as
|
| 327 |
+
`lowdown-labs/fela-power-grid`. The sibling repos are:
|
| 328 |
+
|
| 329 |
+
- `lowdown-labs/fela-genomics`: DNA sequence classification.
|
| 330 |
+
- `lowdown-labs/fela-pdm`: rotating machinery and turbofan health.
|
| 331 |
+
- `lowdown-labs/fela-power-grid` (this repo): probabilistic solar and wind power forecasting.
|
| 332 |
+
- `lowdown-labs/fela-video`: video moment retrieval and temporal grounding.
|
| 333 |
+
- `lowdown-labs/fela-streaming-asr`: streaming CPU speech recognition.
|
| 334 |
+
|
| 335 |
+
These are grouped under the FELA Collection on Hugging Face. The models are independently
|
| 336 |
+
trained per modality and do not share weights, so none carries a `base_model` link.
|
| 337 |
+
|
| 338 |
+
# License
|
| 339 |
+
|
| 340 |
+
Released under the Lowdown Labs Lovely License 1.0 (CC BY-NC 4.0 plus Hippocratic License 3.0). See LICENSE. For most LL models, a commercial license may be available; contact Lowdown Labs.
|
config.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "fela_grid_renewable",
|
| 3 |
+
"description": "Dual path FNO probabilistic power forecaster for solar and wind, trained on GEFCom2014. Outputs 99 quantiles per hour from weather covariates.",
|
| 4 |
+
"architecture": "fno_dual_path",
|
| 5 |
+
"framework": "pytorch",
|
| 6 |
+
"architectures": [
|
| 7 |
+
"FelaGridModel"
|
| 8 |
+
],
|
| 9 |
+
"auto_map": {
|
| 10 |
+
"AutoConfig": "configuration_grid.FelaGridConfig",
|
| 11 |
+
"AutoModel": "modeling_grid.FelaGridModel"
|
| 12 |
+
},
|
| 13 |
+
"default_track": "solar",
|
| 14 |
+
"tracks": {
|
| 15 |
+
"solar": {
|
| 16 |
+
"params": 136780,
|
| 17 |
+
"input_steps": 6,
|
| 18 |
+
"input_features": 20,
|
| 19 |
+
"input_shape": [1, 6, 20],
|
| 20 |
+
"weights_safetensors": "solar.safetensors",
|
| 21 |
+
"dims": {"Fin": 20, "L": 6, "D": 64, "modes": 3, "nblk": 4, "nq": 99, "arch": "dual"}
|
| 22 |
+
},
|
| 23 |
+
"wind": {
|
| 24 |
+
"params": 311554,
|
| 25 |
+
"input_steps": 12,
|
| 26 |
+
"input_features": 15,
|
| 27 |
+
"input_shape": [1, 12, 15],
|
| 28 |
+
"weights_safetensors": "wind.safetensors",
|
| 29 |
+
"dims": {"Fin": 15, "L": 12, "D": 96, "modes": 6, "nblk": 3, "nq": 99, "arch": "dual"}
|
| 30 |
+
}
|
| 31 |
+
},
|
| 32 |
+
"num_quantiles": 99,
|
| 33 |
+
"quantile_levels_percent": "1..99",
|
| 34 |
+
"output_shape": "[batch, 99]",
|
| 35 |
+
"output_units": "power as fraction of site rated capacity (0 to 1), 99 monotone quantiles for the center forecast hour",
|
| 36 |
+
"preprocessing": "standardize the NWP window per feature; RevIN runs inside the model. See modeling.preprocess_nwp",
|
| 37 |
+
"license": "lowdown-labs-lovely-license-1.0"
|
| 38 |
+
}
|
configuration_grid.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from transformers import PretrainedConfig
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class FelaGridConfig(PretrainedConfig):
|
| 5 |
+
model_type = "fela_grid_renewable"
|
| 6 |
+
|
| 7 |
+
def __init__(
|
| 8 |
+
self,
|
| 9 |
+
track="solar",
|
| 10 |
+
Fin=20,
|
| 11 |
+
L=6,
|
| 12 |
+
D=64,
|
| 13 |
+
modes=3,
|
| 14 |
+
nblk=4,
|
| 15 |
+
nq=99,
|
| 16 |
+
arch="dual",
|
| 17 |
+
tracks=None,
|
| 18 |
+
**kwargs,
|
| 19 |
+
):
|
| 20 |
+
if isinstance(tracks, dict) and track in tracks:
|
| 21 |
+
dims = tracks[track].get("dims", {})
|
| 22 |
+
Fin = dims.get("Fin", Fin)
|
| 23 |
+
L = dims.get("L", L)
|
| 24 |
+
D = dims.get("D", D)
|
| 25 |
+
modes = dims.get("modes", modes)
|
| 26 |
+
nblk = dims.get("nblk", nblk)
|
| 27 |
+
nq = dims.get("nq", nq)
|
| 28 |
+
arch = dims.get("arch", arch)
|
| 29 |
+
self.track = track
|
| 30 |
+
self.Fin = Fin
|
| 31 |
+
self.L = L
|
| 32 |
+
self.D = D
|
| 33 |
+
self.modes = modes
|
| 34 |
+
self.nblk = nblk
|
| 35 |
+
self.nq = nq
|
| 36 |
+
self.arch = arch
|
| 37 |
+
if tracks is not None:
|
| 38 |
+
self.tracks = tracks
|
| 39 |
+
super().__init__(**kwargs)
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dd972ee0aae73432de6f35325dd1458555e99ecb12e9c2edf76c793bc94b14b3
|
| 3 |
+
size 747808
|
modeling.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
|
| 7 |
+
CONFIG = None
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def _config():
|
| 11 |
+
global CONFIG
|
| 12 |
+
if CONFIG is None:
|
| 13 |
+
here = os.path.dirname(os.path.abspath(__file__))
|
| 14 |
+
with open(os.path.join(here, "config.json")) as f:
|
| 15 |
+
CONFIG = json.load(f)
|
| 16 |
+
return CONFIG
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class RevIN(nn.Module):
|
| 20 |
+
def __init__(self, C):
|
| 21 |
+
super().__init__()
|
| 22 |
+
self.g = nn.Parameter(torch.ones(C))
|
| 23 |
+
self.b = nn.Parameter(torch.zeros(C))
|
| 24 |
+
|
| 25 |
+
def norm(self, x):
|
| 26 |
+
self.m = x.mean(1, keepdim=True)
|
| 27 |
+
self.s = x.std(1, keepdim=True) + 1e-05
|
| 28 |
+
return (x - self.m) / self.s * self.g + self.b
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class FNO1D(nn.Module):
|
| 32 |
+
def __init__(self, D, modes):
|
| 33 |
+
super().__init__()
|
| 34 |
+
self.modes = modes
|
| 35 |
+
s = 1 / (D * D)
|
| 36 |
+
self.w = nn.Parameter(s * torch.rand(modes, D, D, dtype=torch.cfloat))
|
| 37 |
+
|
| 38 |
+
def forward(self, x):
|
| 39 |
+
P = x.shape[1]
|
| 40 |
+
xf = torch.fft.rfft(x, dim=1)
|
| 41 |
+
m = min(self.modes, xf.shape[1])
|
| 42 |
+
o = torch.zeros_like(xf)
|
| 43 |
+
o[:, :m] = torch.einsum("bpd,pde->bpe", xf[:, :m], self.w[:m])
|
| 44 |
+
return torch.fft.irfft(o, n=P, dim=1)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class Block(nn.Module):
|
| 48 |
+
def __init__(self, D, modes, ff=2, drop=0.0):
|
| 49 |
+
super().__init__()
|
| 50 |
+
self.n1 = nn.LayerNorm(D)
|
| 51 |
+
self.fno = FNO1D(D, modes)
|
| 52 |
+
self.d1 = nn.Dropout(drop)
|
| 53 |
+
self.n2 = nn.LayerNorm(D)
|
| 54 |
+
self.ff = nn.Sequential(
|
| 55 |
+
nn.Linear(D, D * ff), nn.GELU(), nn.Dropout(drop), nn.Linear(D * ff, D)
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
def forward(self, x):
|
| 59 |
+
x = x + self.d1(self.fno(self.n1(x)))
|
| 60 |
+
return x + self.ff(self.n2(x))
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class FELA_Grid(nn.Module):
|
| 64 |
+
def __init__(self, Fin, L, D=96, modes=6, nblk=3, nq=99, arch="dual"):
|
| 65 |
+
super().__init__()
|
| 66 |
+
self.L = L
|
| 67 |
+
self.arch = arch
|
| 68 |
+
self.center = L // 2
|
| 69 |
+
self.nq = nq
|
| 70 |
+
self.revin = RevIN(Fin)
|
| 71 |
+
self.embed = nn.Linear(Fin, D)
|
| 72 |
+
self.pos = nn.Parameter(0.02 * torch.randn(1, L, D))
|
| 73 |
+
self.blocks = nn.ModuleList([Block(D, modes) for _ in range(nblk)])
|
| 74 |
+
self.norm = nn.LayerNorm(D)
|
| 75 |
+
if arch == "dual":
|
| 76 |
+
self.direct = nn.Sequential(
|
| 77 |
+
nn.Linear(Fin, D), nn.GELU(), nn.Linear(D, D), nn.GELU()
|
| 78 |
+
)
|
| 79 |
+
fuse_in = 2 * D
|
| 80 |
+
else:
|
| 81 |
+
fuse_in = D
|
| 82 |
+
self.med = nn.Linear(fuse_in, 1)
|
| 83 |
+
self.spread = nn.Linear(fuse_in, nq)
|
| 84 |
+
self.register_buffer("qidx", torch.arange(nq))
|
| 85 |
+
|
| 86 |
+
def forward(self, x):
|
| 87 |
+
xc = x[:, self.center]
|
| 88 |
+
xn = self.revin.norm(x)
|
| 89 |
+
h = self.embed(xn) + self.pos
|
| 90 |
+
for b in self.blocks:
|
| 91 |
+
h = b(h)
|
| 92 |
+
ctx = self.norm(h)[:, self.center]
|
| 93 |
+
if self.arch == "dual":
|
| 94 |
+
z = torch.cat([ctx, self.direct(xc)], dim=1)
|
| 95 |
+
else:
|
| 96 |
+
z = ctx
|
| 97 |
+
med = torch.sigmoid(self.med(z))
|
| 98 |
+
w = F.softplus(self.spread(z))
|
| 99 |
+
half = self.nq // 2
|
| 100 |
+
below = -torch.flip(torch.cumsum(torch.flip(w[:, :half], [1]), 1), [1])
|
| 101 |
+
above = torch.cumsum(w[:, half + 1 :], 1)
|
| 102 |
+
offs = (
|
| 103 |
+
torch.cat([below, torch.zeros_like(w[:, half : half + 1]), above], dim=1)
|
| 104 |
+
* 0.05
|
| 105 |
+
)
|
| 106 |
+
return torch.clamp(med + offs, 0, 1)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def expected_shape(track):
|
| 110 |
+
t = _config()["tracks"][track]
|
| 111 |
+
return (t["input_steps"], t["input_features"])
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def validate_input(x, track):
|
| 115 |
+
steps, feats = expected_shape(track)
|
| 116 |
+
if not isinstance(x, torch.Tensor):
|
| 117 |
+
raise TypeError(f"Expected a torch.Tensor, got {type(x)}")
|
| 118 |
+
if x.dim() != 3:
|
| 119 |
+
raise ValueError(
|
| 120 |
+
f"{track}: expected a 3-D tensor (batch, steps, features), got shape {tuple(x.shape)}"
|
| 121 |
+
)
|
| 122 |
+
if x.shape[1] != steps or x.shape[2] != feats:
|
| 123 |
+
raise ValueError(
|
| 124 |
+
f"{track}: expected window (batch, {steps}, {feats}), got {tuple(x.shape)}. Solar windows are (.,6,20); wind windows are (.,12,15)."
|
| 125 |
+
)
|
| 126 |
+
return x
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def preprocess_nwp(raw_window, track, mean=None, std=None):
|
| 130 |
+
x = torch.as_tensor(raw_window, dtype=torch.float32)
|
| 131 |
+
if x.dim() == 2:
|
| 132 |
+
x = x.unsqueeze(0)
|
| 133 |
+
if mean is not None and std is not None:
|
| 134 |
+
mean = torch.as_tensor(mean, dtype=torch.float32)
|
| 135 |
+
std = torch.as_tensor(std, dtype=torch.float32)
|
| 136 |
+
x = (x - mean) / torch.clamp(std, min=1e-06)
|
| 137 |
+
else:
|
| 138 |
+
m = x.mean(dim=(0, 1), keepdim=True)
|
| 139 |
+
s = x.std(dim=(0, 1), keepdim=True)
|
| 140 |
+
x = (x - m) / torch.clamp(s, min=1e-06)
|
| 141 |
+
return validate_input(x, track)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def _build_from_state(state, track):
|
| 145 |
+
dims = _config()["tracks"][track]["dims"]
|
| 146 |
+
model = FELA_Grid(
|
| 147 |
+
dims["Fin"],
|
| 148 |
+
dims["L"],
|
| 149 |
+
D=dims["D"],
|
| 150 |
+
modes=dims["modes"],
|
| 151 |
+
nblk=dims["nblk"],
|
| 152 |
+
nq=dims["nq"],
|
| 153 |
+
arch=dims.get("arch", "dual"),
|
| 154 |
+
)
|
| 155 |
+
ref = model.state_dict()
|
| 156 |
+
fixed = {}
|
| 157 |
+
for k, v in state.items():
|
| 158 |
+
t = ref.get(k)
|
| 159 |
+
if t is not None and t.is_complex() and (not v.is_complex()):
|
| 160 |
+
v = torch.view_as_complex(v.contiguous())
|
| 161 |
+
fixed[k] = v
|
| 162 |
+
fixed["qidx"] = model.qidx.clone()
|
| 163 |
+
model.load_state_dict(fixed, strict=True)
|
| 164 |
+
return model
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def load_model(path_or_repo, track="solar", filename=None):
|
| 168 |
+
path = path_or_repo
|
| 169 |
+
fname = filename or _config()["tracks"][track]["weights_safetensors"]
|
| 170 |
+
if os.path.isdir(path):
|
| 171 |
+
path = os.path.join(path, fname)
|
| 172 |
+
elif not os.path.exists(path):
|
| 173 |
+
from huggingface_hub import hf_hub_download
|
| 174 |
+
|
| 175 |
+
path = hf_hub_download(path_or_repo, fname)
|
| 176 |
+
from safetensors.torch import load_file
|
| 177 |
+
|
| 178 |
+
state = load_file(path)
|
| 179 |
+
model = _build_from_state(state, track)
|
| 180 |
+
model.eval()
|
| 181 |
+
return model
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def from_pretrained(repo_id, track="solar"):
|
| 185 |
+
return load_model(repo_id, track=track)
|
modeling_grid.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import types
|
| 4 |
+
|
| 5 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
from transformers import PreTrainedModel
|
| 9 |
+
from transformers.modeling_outputs import CausalLMOutput
|
| 10 |
+
|
| 11 |
+
from .configuration_grid import FelaGridConfig
|
| 12 |
+
from .modeling import FELA_Grid, FNO1D
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _fno1d_forward(self, x):
|
| 16 |
+
w = torch.view_as_complex(self.w)
|
| 17 |
+
P = x.shape[1]
|
| 18 |
+
xf = torch.fft.rfft(x, dim=1)
|
| 19 |
+
mm = min(self.modes, xf.shape[1])
|
| 20 |
+
o = torch.zeros_like(xf)
|
| 21 |
+
o[:, :mm] = torch.einsum("bpd,pde->bpe", xf[:, :mm], w[:mm])
|
| 22 |
+
return torch.fft.irfft(o, n=P, dim=1)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _realify(model):
|
| 26 |
+
for m in model.modules():
|
| 27 |
+
if isinstance(m, FNO1D):
|
| 28 |
+
m.w = nn.Parameter(torch.view_as_real(m.w.detach()).contiguous())
|
| 29 |
+
m.forward = types.MethodType(_fno1d_forward, m)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class FelaGridModel(PreTrainedModel):
|
| 33 |
+
config_class = FelaGridConfig
|
| 34 |
+
base_model_prefix = "model"
|
| 35 |
+
main_input_name = "x"
|
| 36 |
+
|
| 37 |
+
def __init__(self, config):
|
| 38 |
+
super().__init__(config)
|
| 39 |
+
self.model = FELA_Grid(
|
| 40 |
+
config.Fin,
|
| 41 |
+
config.L,
|
| 42 |
+
D=config.D,
|
| 43 |
+
modes=config.modes,
|
| 44 |
+
nblk=config.nblk,
|
| 45 |
+
nq=config.nq,
|
| 46 |
+
arch=config.arch,
|
| 47 |
+
)
|
| 48 |
+
self.model._non_persistent_buffers_set.add("qidx")
|
| 49 |
+
_realify(self.model)
|
| 50 |
+
self.post_init()
|
| 51 |
+
|
| 52 |
+
def forward(self, x=None, input_values=None, **kwargs):
|
| 53 |
+
if x is None:
|
| 54 |
+
x = input_values
|
| 55 |
+
out = self.model(x)
|
| 56 |
+
return CausalLMOutput(logits=out)
|
quickstart/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Quickstart: FELA Grid Renewable
|
| 2 |
+
|
| 3 |
+
A minimal example that loads the forecaster, runs one weather input window, and prints the
|
| 4 |
+
P10 to P90 power band for the first forecast hour. It runs on CPU; no GPU is required.
|
| 5 |
+
|
| 6 |
+
## Install
|
| 7 |
+
|
| 8 |
+
Pinned versions, runs from a clean virtual environment:
|
| 9 |
+
|
| 10 |
+
```
|
| 11 |
+
pip install -r requirements.txt
|
| 12 |
+
```
|
| 13 |
+
|
| 14 |
+
## Get the weights
|
| 15 |
+
|
| 16 |
+
The safetensors weights ship in the model repo (`solar.safetensors`, `wind.safetensors`).
|
| 17 |
+
`--weights` defaults to the track's safetensors file, or point it at a local path.
|
| 18 |
+
|
| 19 |
+
## Run
|
| 20 |
+
|
| 21 |
+
Solar (default):
|
| 22 |
+
|
| 23 |
+
```
|
| 24 |
+
python run.py --track solar --weights /path/to/solar.safetensors
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
Wind:
|
| 28 |
+
|
| 29 |
+
```
|
| 30 |
+
python run.py --track wind --weights /path/to/wind.safetensors
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
The script loads with the bundled `modeling.load_model` (a few line load) and preprocesses
|
| 34 |
+
the window with `modeling.preprocess_nwp`, which standardizes it and validates the shape
|
| 35 |
+
(it fails clearly on the wrong shape).
|
| 36 |
+
|
| 37 |
+
## What you should see
|
| 38 |
+
|
| 39 |
+
The script prints the output shape `(1, 99)` and, for the center forecast hour,
|
| 40 |
+
the P10 (low), P50 (median), and P90 (high) power levels as a fraction of site capacity,
|
| 41 |
+
plus the width of the P10 to P90 uncertainty band. Input shapes are solar `(1, 6, 20)`,
|
| 42 |
+
wind `(1, 12, 15)`.
|
| 43 |
+
|
| 44 |
+
The example uses a random input window so it runs without a data download. Replace it with
|
| 45 |
+
a real NWP window (the same shape) to get a real forecast.
|
quickstart/requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch==2.3.1
|
| 2 |
+
numpy==1.26.4
|
| 3 |
+
safetensors==0.4.3
|
| 4 |
+
huggingface_hub==0.23.4
|
quickstart/run.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 7 |
+
import modeling
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def main():
|
| 11 |
+
ap = argparse.ArgumentParser()
|
| 12 |
+
ap.add_argument("--track", choices=["solar", "wind"], default="solar")
|
| 13 |
+
ap.add_argument(
|
| 14 |
+
"--weights",
|
| 15 |
+
default=None,
|
| 16 |
+
help="path to the track's safetensors (defaults to <track>.safetensors)",
|
| 17 |
+
)
|
| 18 |
+
args = ap.parse_args()
|
| 19 |
+
weights = args.weights or f"{args.track}.safetensors"
|
| 20 |
+
steps, feats = modeling.expected_shape(args.track)
|
| 21 |
+
raw_window = torch.randn(steps, feats)
|
| 22 |
+
nwp = modeling.preprocess_nwp(raw_window, track=args.track)
|
| 23 |
+
model = modeling.load_model(weights, track=args.track)
|
| 24 |
+
with torch.no_grad():
|
| 25 |
+
quantiles = model(nwp)
|
| 26 |
+
p10 = quantiles[0, 9].item()
|
| 27 |
+
p50 = quantiles[0, 49].item()
|
| 28 |
+
p90 = quantiles[0, 89].item()
|
| 29 |
+
print(f"Track: {args.track}")
|
| 30 |
+
print(
|
| 31 |
+
f"Output shape: {tuple(quantiles.shape)} (batch, 99 quantiles for the center hour)"
|
| 32 |
+
)
|
| 33 |
+
print("Center forecast hour, power as fraction of site capacity:")
|
| 34 |
+
print(f" P10 (low): {p10:.4f}")
|
| 35 |
+
print(f" P50 (median): {p50:.4f}")
|
| 36 |
+
print(f" P90 (high): {p90:.4f}")
|
| 37 |
+
print(f" P10..P90 uncertainty band width: {p90 - p10:.4f}")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
if __name__ == "__main__":
|
| 41 |
+
main()
|
solar.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dd972ee0aae73432de6f35325dd1458555e99ecb12e9c2edf76c793bc94b14b3
|
| 3 |
+
size 747808
|
space/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: FELA Grid Renewable
|
| 3 |
+
emoji: power
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: gray
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 4.44.0
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
license: other
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
# FELA Grid Renewable playground
|
| 14 |
+
|
| 15 |
+
Pick solar or wind, pick a sample weather window, and see the model's probabilistic power
|
| 16 |
+
forecast: the P10 low estimate, the P50 median, and the P90 high estimate, all as a
|
| 17 |
+
fraction of the site's rated capacity. The gap between P10 and P90 is the uncertainty band
|
| 18 |
+
a grid operator plans against.
|
| 19 |
+
|
| 20 |
+
## Play data
|
| 21 |
+
|
| 22 |
+
The sample weather windows are a small GEFCom2014 style set of numerical weather
|
| 23 |
+
prediction (NWP) inputs. GEFCom2014 is the public Global Energy Forecasting Competition
|
| 24 |
+
2014 dataset (Hong, Pinson, Fan, Zareipour, Troccoli, Hyndman, 2016, International Journal
|
| 25 |
+
of Forecasting 32(3)). It is the public competition release; check the competition terms
|
| 26 |
+
before rehosting it. The samples are kept small so the Space loads fast.
|
| 27 |
+
|
| 28 |
+
If the bundled sample (`solar_demo.json`, `wind_demo.json`) is not present, the app falls
|
| 29 |
+
back to synthetic NWP windows of the correct shape so the Space still runs. The synthetic
|
| 30 |
+
fallback shows the input and output shapes working, not a real site forecast.
|
| 31 |
+
|
| 32 |
+
Input shapes are solar `(1, 6, 20)`, wind `(1, 12, 15)`.
|
| 33 |
+
|
| 34 |
+
## Weights
|
| 35 |
+
|
| 36 |
+
The safetensors weight files (`solar.safetensors`, `wind.safetensors`) ship with the model
|
| 37 |
+
and load through the bundled `modeling.load_model`. If they are absent the app shows a notice.
|
| 38 |
+
|
| 39 |
+
## License
|
| 40 |
+
|
| 41 |
+
Released under the Lowdown Labs Lovely License 1.0. See the model repo for the
|
| 42 |
+
full license text.
|
space/app.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
import gradio as gr
|
| 5 |
+
import numpy as np
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 9 |
+
sys.path.insert(0, os.path.join(HERE, ".."))
|
| 10 |
+
SHAPES = {"solar": (6, 20), "wind": (12, 15)}
|
| 11 |
+
WEIGHTS_SAFE = {
|
| 12 |
+
"solar": os.path.join(HERE, "solar.safetensors"),
|
| 13 |
+
"wind": os.path.join(HERE, "wind.safetensors"),
|
| 14 |
+
}
|
| 15 |
+
WEIGHTS_PT = {
|
| 16 |
+
"solar": os.path.join(HERE, "solar.pt"),
|
| 17 |
+
"wind": os.path.join(HERE, "wind.pt"),
|
| 18 |
+
}
|
| 19 |
+
SAMPLES = {
|
| 20 |
+
"solar": os.path.join(HERE, "solar_demo.json"),
|
| 21 |
+
"wind": os.path.join(HERE, "wind_demo.json"),
|
| 22 |
+
}
|
| 23 |
+
_models = {}
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def load_model(track):
|
| 27 |
+
if track not in _models:
|
| 28 |
+
if os.path.exists(WEIGHTS_SAFE[track]):
|
| 29 |
+
import modeling
|
| 30 |
+
|
| 31 |
+
m = modeling.load_model(WEIGHTS_SAFE[track], track=track)
|
| 32 |
+
m.eval()
|
| 33 |
+
_models[track] = m
|
| 34 |
+
elif os.path.exists(WEIGHTS_PT[track]):
|
| 35 |
+
m = torch.load(WEIGHTS_PT[track], map_location="cpu", weights_only=False)
|
| 36 |
+
m.eval()
|
| 37 |
+
_models[track] = m
|
| 38 |
+
else:
|
| 39 |
+
_models[track] = None
|
| 40 |
+
return _models[track]
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def load_sample_windows(track):
|
| 44 |
+
path = SAMPLES[track]
|
| 45 |
+
steps, feats = SHAPES[track]
|
| 46 |
+
if os.path.exists(path):
|
| 47 |
+
with open(path) as f:
|
| 48 |
+
data = json.load(f)
|
| 49 |
+
windows = data.get("windows", data) if isinstance(data, dict) else data
|
| 50 |
+
return [np.asarray(w, dtype=np.float32).reshape(steps, feats) for w in windows]
|
| 51 |
+
rng = np.random.default_rng(0)
|
| 52 |
+
return [rng.standard_normal((steps, feats)).astype(np.float32) for _ in range(8)]
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def forecast(track, sample_idx):
|
| 56 |
+
steps, feats = SHAPES[track]
|
| 57 |
+
windows = load_sample_windows(track)
|
| 58 |
+
sample_idx = int(sample_idx) % len(windows)
|
| 59 |
+
nwp = torch.from_numpy(windows[sample_idx]).unsqueeze(0)
|
| 60 |
+
model = load_model(track)
|
| 61 |
+
if model is None:
|
| 62 |
+
return "Weights are not found in this Space. Ensure solar.safetensors / wind.safetensors are present next to app.py."
|
| 63 |
+
with torch.no_grad():
|
| 64 |
+
q = model(nwp)
|
| 65 |
+
q = q[0].cpu().numpy()
|
| 66 |
+
p10, p50, p90 = (q[9], q[49], q[89])
|
| 67 |
+
lines = [
|
| 68 |
+
f"Track: {track}",
|
| 69 |
+
f"Sample window {sample_idx} of {len(windows)}",
|
| 70 |
+
"First forecast hour (power as a fraction of site capacity, 0 to 1):",
|
| 71 |
+
f" P10 (low estimate): {p10:.4f}",
|
| 72 |
+
f" P50 (median forecast): {p50:.4f}",
|
| 73 |
+
f" P90 (high estimate): {p90:.4f}",
|
| 74 |
+
f" P10 to P90 band width: {p90 - p10:.4f}",
|
| 75 |
+
"",
|
| 76 |
+
"The P10 to P90 band is the range the actual output should fall in most of the",
|
| 77 |
+
"time. A wide band means the weather input is uncertain for this hour.",
|
| 78 |
+
]
|
| 79 |
+
return "\n".join(lines)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
with gr.Blocks(title="FELA Grid Renewable") as demo:
|
| 83 |
+
gr.Markdown(
|
| 84 |
+
"# FELA Grid Renewable\nProbabilistic solar and wind power forecasting that runs on a microcontroller at the station. Pick a track and a sample weather window to see the forecast range (P10 low, P50 median, P90 high), as a fraction of the site's rated capacity.\n\nPlay data is a small GEFCom2014-style sample of numerical weather prediction windows (public competition data, Hong et al. 2016). See the Space README."
|
| 85 |
+
)
|
| 86 |
+
with gr.Row():
|
| 87 |
+
track = gr.Radio(["solar", "wind"], value="solar", label="Track")
|
| 88 |
+
sample_idx = gr.Slider(0, 7, value=0, step=1, label="Sample weather window")
|
| 89 |
+
out = gr.Textbox(label="Forecast", lines=12)
|
| 90 |
+
btn = gr.Button("Forecast")
|
| 91 |
+
btn.click(forecast, inputs=[track, sample_idx], outputs=out)
|
| 92 |
+
track.change(forecast, inputs=[track, sample_idx], outputs=out)
|
| 93 |
+
sample_idx.change(forecast, inputs=[track, sample_idx], outputs=out)
|
| 94 |
+
if __name__ == "__main__":
|
| 95 |
+
demo.launch()
|
space/requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch>=2.0
|
| 2 |
+
numpy
|
| 3 |
+
gradio>=4.0
|
streaming/manifest.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model": "grid-renewable",
|
| 3 |
+
"format": "fp16-streaming",
|
| 4 |
+
"note": "load order is smallest-first for progressive/streaming load",
|
| 5 |
+
"files": [
|
| 6 |
+
{
|
| 7 |
+
"file": "model_fp16.safetensors",
|
| 8 |
+
"source": "model.safetensors",
|
| 9 |
+
"dtype": "fp16",
|
| 10 |
+
"bytes": 375928,
|
| 11 |
+
"approx_mb": 0.359
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"file": "solar_fp16.safetensors",
|
| 15 |
+
"source": "solar.safetensors",
|
| 16 |
+
"dtype": "fp16",
|
| 17 |
+
"bytes": 375928,
|
| 18 |
+
"approx_mb": 0.359
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"file": "wind_fp16.safetensors",
|
| 22 |
+
"source": "wind.safetensors",
|
| 23 |
+
"dtype": "fp16",
|
| 24 |
+
"bytes": 958228,
|
| 25 |
+
"approx_mb": 0.914
|
| 26 |
+
}
|
| 27 |
+
]
|
| 28 |
+
}
|
streaming/model_fp16.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:03cb0a075cdd53dbaf9b1ccb54b29ed450d39b34f65a6cfb4dcd7cd58bd51baf
|
| 3 |
+
size 375928
|
streaming/solar_fp16.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:03cb0a075cdd53dbaf9b1ccb54b29ed450d39b34f65a6cfb4dcd7cd58bd51baf
|
| 3 |
+
size 375928
|
streaming/wind_fp16.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c98d362d96d949a64c9afbfcc0979813c2f9cbbefef54989cc093ea2ae2142bd
|
| 3 |
+
size 958228
|
train.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys, time, os, numpy as np, pandas as pd, torch, torch.nn as nn, torch.nn.functional as F
|
| 2 |
+
|
| 3 |
+
dev = "cuda" if torch.cuda.is_available() else "cpu"
|
| 4 |
+
torch.manual_seed(2024)
|
| 5 |
+
np.random.seed(2024)
|
| 6 |
+
if dev == "cpu" and os.environ.get("OMP_NUM_THREADS"):
|
| 7 |
+
torch.set_num_threads(int(os.environ["OMP_NUM_THREADS"]))
|
| 8 |
+
valflags = {"--save", "--arch", "--lr"}
|
| 9 |
+
pos = []
|
| 10 |
+
av = sys.argv[1:]
|
| 11 |
+
i = 0
|
| 12 |
+
while i < len(av):
|
| 13 |
+
if av[i] in valflags:
|
| 14 |
+
i += 2
|
| 15 |
+
continue
|
| 16 |
+
if av[i].startswith("--"):
|
| 17 |
+
i += 1
|
| 18 |
+
continue
|
| 19 |
+
pos.append(av[i])
|
| 20 |
+
i += 1
|
| 21 |
+
track = pos[0]
|
| 22 |
+
dL, dm, dD, dn = {"solar": (6, 3, 64, 4), "wind": (12, 6, 96, 3)}[track]
|
| 23 |
+
L = int(pos[1]) if len(pos) > 1 else dL
|
| 24 |
+
modes = int(pos[2]) if len(pos) > 2 else dm
|
| 25 |
+
D = int(pos[3]) if len(pos) > 3 else dD
|
| 26 |
+
nblk = int(pos[4]) if len(pos) > 4 else dn
|
| 27 |
+
ep = int(pos[5]) if len(pos) > 5 else 80
|
| 28 |
+
save = sys.argv[sys.argv.index("--save") + 1] if "--save" in sys.argv else None
|
| 29 |
+
arch = sys.argv[sys.argv.index("--arch") + 1] if "--arch" in sys.argv else "dual"
|
| 30 |
+
lr = float(sys.argv[sys.argv.index("--lr") + 1]) if "--lr" in sys.argv else 0.002
|
| 31 |
+
smoke = "--smoke" in sys.argv
|
| 32 |
+
prep = "/workspace/gefcom/prep"
|
| 33 |
+
qs = np.arange(1, 100) / 100.0
|
| 34 |
+
nwp = {
|
| 35 |
+
"solar": [
|
| 36 |
+
"VAR78",
|
| 37 |
+
"VAR79",
|
| 38 |
+
"VAR134",
|
| 39 |
+
"VAR157",
|
| 40 |
+
"VAR164",
|
| 41 |
+
"VAR165",
|
| 42 |
+
"VAR166",
|
| 43 |
+
"VAR167",
|
| 44 |
+
"VAR169",
|
| 45 |
+
"VAR175",
|
| 46 |
+
"VAR178",
|
| 47 |
+
"VAR228",
|
| 48 |
+
],
|
| 49 |
+
"wind": ["U10", "V10", "U100", "V100"],
|
| 50 |
+
}[track]
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def build():
|
| 54 |
+
df = (
|
| 55 |
+
pd.read_parquet(f"{prep}/{track}.parquet")
|
| 56 |
+
.sort_values(["ZONEID", "TIMESTAMP"])
|
| 57 |
+
.reset_index(drop=True)
|
| 58 |
+
)
|
| 59 |
+
df["hour"] = df.TIMESTAMP.dt.hour
|
| 60 |
+
df["doy"] = df.TIMESTAMP.dt.dayofyear
|
| 61 |
+
df["hsin"] = np.sin(2 * np.pi * df.hour / 24)
|
| 62 |
+
df["hcos"] = np.cos(2 * np.pi * df.hour / 24)
|
| 63 |
+
df["dsin"] = np.sin(2 * np.pi * df.doy / 365.25)
|
| 64 |
+
df["dcos"] = np.cos(2 * np.pi * df.doy / 365.25)
|
| 65 |
+
feats = list(nwp) + ["hsin", "hcos", "dsin", "dcos"]
|
| 66 |
+
if track == "wind":
|
| 67 |
+
df["ws10"] = np.hypot(df.U10, df.V10)
|
| 68 |
+
df["ws100"] = np.hypot(df.U100, df.V100)
|
| 69 |
+
df["wd100"] = np.arctan2(df.V100, df.U100)
|
| 70 |
+
df["wds"] = np.sin(df.wd100)
|
| 71 |
+
df["wdc"] = np.cos(df.wd100)
|
| 72 |
+
df["ws100_2"] = df.ws100**2
|
| 73 |
+
df["ws100_3"] = df.ws100**3
|
| 74 |
+
df["shear"] = df.ws100 - df.ws10
|
| 75 |
+
feats += ["ws10", "ws100", "wds", "wdc", "ws100_2", "ws100_3", "shear"]
|
| 76 |
+
else:
|
| 77 |
+
df["csi"] = df.VAR169 / (df.VAR178 + 1000.0)
|
| 78 |
+
df["cloud2"] = df.VAR164**2
|
| 79 |
+
df["temp_c"] = df.VAR167 - 273.15
|
| 80 |
+
df["daylight"] = (df.VAR178 > 10000.0).astype(np.float32)
|
| 81 |
+
feats += ["csi", "cloud2", "temp_c", "daylight"]
|
| 82 |
+
tr = df.is_test == 0
|
| 83 |
+
mu = df.loc[tr, feats].mean()
|
| 84 |
+
sd = df.loc[tr, feats].std() + 1e-06
|
| 85 |
+
df[feats] = (df[feats] - mu) / sd
|
| 86 |
+
df["y"] = df["y"].fillna(0.0)
|
| 87 |
+
X, Y, M = ([], [], [])
|
| 88 |
+
half = L // 2
|
| 89 |
+
for z, g in df.groupby("ZONEID"):
|
| 90 |
+
g = g.reset_index(drop=True)
|
| 91 |
+
fv = g[feats].values.astype(np.float32)
|
| 92 |
+
powr = g["y"].values.astype(np.float32)
|
| 93 |
+
iste = g["is_test"].values
|
| 94 |
+
T = len(g)
|
| 95 |
+
for i in range(T):
|
| 96 |
+
a = i - half
|
| 97 |
+
b = i - half + L
|
| 98 |
+
if a < 0 or b > T:
|
| 99 |
+
continue
|
| 100 |
+
X.append(fv[a:b])
|
| 101 |
+
Y.append(powr[i])
|
| 102 |
+
M.append(iste[i])
|
| 103 |
+
X = torch.tensor(np.array(X, dtype=np.float32))
|
| 104 |
+
Y = torch.tensor(np.array(Y, dtype=np.float32))
|
| 105 |
+
return (X, Y, np.array(M), df, feats)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
class RevIN(nn.Module):
|
| 109 |
+
def __init__(self, C):
|
| 110 |
+
super().__init__()
|
| 111 |
+
self.g = nn.Parameter(torch.ones(C))
|
| 112 |
+
self.b = nn.Parameter(torch.zeros(C))
|
| 113 |
+
|
| 114 |
+
def norm(self, x):
|
| 115 |
+
self.m = x.mean(1, keepdim=True)
|
| 116 |
+
self.s = x.std(1, keepdim=True) + 1e-05
|
| 117 |
+
return (x - self.m) / self.s * self.g + self.b
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
class FNO1D(nn.Module):
|
| 121 |
+
def __init__(self, D, modes):
|
| 122 |
+
super().__init__()
|
| 123 |
+
self.modes = modes
|
| 124 |
+
s = 1 / (D * D)
|
| 125 |
+
self.w = nn.Parameter(s * torch.rand(modes, D, D, dtype=torch.cfloat))
|
| 126 |
+
|
| 127 |
+
def forward(self, x):
|
| 128 |
+
P = x.shape[1]
|
| 129 |
+
xf = torch.fft.rfft(x, dim=1)
|
| 130 |
+
m = min(self.modes, xf.shape[1])
|
| 131 |
+
o = torch.zeros_like(xf)
|
| 132 |
+
o[:, :m] = torch.einsum("bpd,pde->bpe", xf[:, :m], self.w[:m])
|
| 133 |
+
return torch.fft.irfft(o, n=P, dim=1)
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
class Block(nn.Module):
|
| 137 |
+
def __init__(self, D, modes, ff=2, drop=0.1):
|
| 138 |
+
super().__init__()
|
| 139 |
+
self.n1 = nn.LayerNorm(D)
|
| 140 |
+
self.fno = FNO1D(D, modes)
|
| 141 |
+
self.d1 = nn.Dropout(drop)
|
| 142 |
+
self.n2 = nn.LayerNorm(D)
|
| 143 |
+
self.ff = nn.Sequential(
|
| 144 |
+
nn.Linear(D, D * ff), nn.GELU(), nn.Dropout(drop), nn.Linear(D * ff, D)
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
def forward(self, x):
|
| 148 |
+
x = x + self.d1(self.fno(self.n1(x)))
|
| 149 |
+
return x + self.ff(self.n2(x))
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
class FELA_Grid(nn.Module):
|
| 153 |
+
def __init__(self, Fin, L, D=96, modes=6, nblk=3, nq=99, arch="dual"):
|
| 154 |
+
super().__init__()
|
| 155 |
+
self.L = L
|
| 156 |
+
self.arch = arch
|
| 157 |
+
self.center = L // 2
|
| 158 |
+
self.nq = nq
|
| 159 |
+
self.revin = RevIN(Fin)
|
| 160 |
+
self.embed = nn.Linear(Fin, D)
|
| 161 |
+
self.pos = nn.Parameter(0.02 * torch.randn(1, L, D))
|
| 162 |
+
self.blocks = nn.ModuleList([Block(D, modes) for _ in range(nblk)])
|
| 163 |
+
self.norm = nn.LayerNorm(D)
|
| 164 |
+
if arch == "dual":
|
| 165 |
+
self.direct = nn.Sequential(
|
| 166 |
+
nn.Linear(Fin, D), nn.GELU(), nn.Linear(D, D), nn.GELU()
|
| 167 |
+
)
|
| 168 |
+
fuse_in = 2 * D
|
| 169 |
+
else:
|
| 170 |
+
fuse_in = D
|
| 171 |
+
self.med = nn.Linear(fuse_in, 1)
|
| 172 |
+
self.spread = nn.Linear(fuse_in, nq)
|
| 173 |
+
self.register_buffer("qidx", torch.arange(nq))
|
| 174 |
+
|
| 175 |
+
def forward(self, x):
|
| 176 |
+
xc = x[:, self.center]
|
| 177 |
+
xn = self.revin.norm(x)
|
| 178 |
+
h = self.embed(xn) + self.pos
|
| 179 |
+
for b in self.blocks:
|
| 180 |
+
h = b(h)
|
| 181 |
+
ctx = self.norm(h)[:, self.center]
|
| 182 |
+
z = torch.cat([ctx, self.direct(xc)], dim=1) if self.arch == "dual" else ctx
|
| 183 |
+
med = torch.sigmoid(self.med(z))
|
| 184 |
+
w = F.softplus(self.spread(z))
|
| 185 |
+
half = self.nq // 2
|
| 186 |
+
below = -torch.flip(torch.cumsum(torch.flip(w[:, :half], [1]), 1), [1])
|
| 187 |
+
above = torch.cumsum(w[:, half + 1 :], 1)
|
| 188 |
+
offs = (
|
| 189 |
+
torch.cat([below, torch.zeros_like(w[:, half : half + 1]), above], dim=1)
|
| 190 |
+
* 0.05
|
| 191 |
+
)
|
| 192 |
+
return torch.clamp(med + offs, 0, 1)
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def pinball(pred, y, qs):
|
| 196 |
+
y = y[:, None]
|
| 197 |
+
e = y - pred
|
| 198 |
+
return torch.maximum(qs[None, :] * e, (qs[None, :] - 1) * e).mean()
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def main():
|
| 202 |
+
X, Y, M, df, feats = build()
|
| 203 |
+
Fin = X.shape[2]
|
| 204 |
+
tr_idx = np.where(M == 0)[0]
|
| 205 |
+
te_idx = np.where(M == 1)[0]
|
| 206 |
+
yte = Y[te_idx].numpy()
|
| 207 |
+
keep = ~np.isnan(yte)
|
| 208 |
+
te_idx = te_idx[keep]
|
| 209 |
+
nval = int(len(tr_idx) * 0.06)
|
| 210 |
+
va_idx = tr_idx[-nval:]
|
| 211 |
+
tr_idx = tr_idx[:-nval]
|
| 212 |
+
assert len(te_idx) == {"solar": 2154, "wind": 7390}[track]
|
| 213 |
+
if smoke:
|
| 214 |
+
print(
|
| 215 |
+
f"{track} Test_windows {len(te_idx)} train {len(tr_idx)} val {len(va_idx)} zones {df.ZONEID.nunique()}"
|
| 216 |
+
)
|
| 217 |
+
return
|
| 218 |
+
qst = torch.tensor(qs, dtype=torch.float32, device=dev)
|
| 219 |
+
Xtr, Ytr = (X[tr_idx].to(dev), Y[tr_idx].to(dev))
|
| 220 |
+
Xva, Yva = (X[va_idx].to(dev), Y[va_idx].to(dev))
|
| 221 |
+
Xte, Yte = (X[te_idx].to(dev), Y[te_idx].to(dev))
|
| 222 |
+
m = FELA_Grid(Fin, L, D=D, modes=modes, nblk=nblk, arch=arch).to(dev)
|
| 223 |
+
npar = sum((p.numel() for p in m.parameters()))
|
| 224 |
+
print(
|
| 225 |
+
f"[{track}] arch={arch} L={L} modes={modes} D={D} nblk={nblk} Fin={Fin} | train {len(Xtr)} val {len(Xva)} test {len(Xte)} | {npar / 1000.0:.0f}K"
|
| 226 |
+
)
|
| 227 |
+
opt = torch.optim.Adam(m.parameters(), lr=lr, weight_decay=1e-05)
|
| 228 |
+
sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, ep)
|
| 229 |
+
bs = 512
|
| 230 |
+
|
| 231 |
+
def ev(Xs, Ys):
|
| 232 |
+
m.eval()
|
| 233 |
+
with torch.no_grad():
|
| 234 |
+
ps = [m(Xs[i : i + 8192]) for i in range(0, len(Xs), 8192)]
|
| 235 |
+
p = torch.cat(ps)
|
| 236 |
+
return (pinball(p, Ys, qst).item(), p)
|
| 237 |
+
|
| 238 |
+
best = 1000000000.0
|
| 239 |
+
bstate = None
|
| 240 |
+
bad = 0
|
| 241 |
+
for e in range(ep):
|
| 242 |
+
m.train()
|
| 243 |
+
perm = torch.randperm(len(Xtr), device=dev)
|
| 244 |
+
for i in range(0, len(Xtr) - bs, bs):
|
| 245 |
+
idx = perm[i : i + bs]
|
| 246 |
+
loss = pinball(m(Xtr[idx]), Ytr[idx], qst)
|
| 247 |
+
opt.zero_grad()
|
| 248 |
+
loss.backward()
|
| 249 |
+
opt.step()
|
| 250 |
+
sched.step()
|
| 251 |
+
vpb, _ = ev(Xva, Yva)
|
| 252 |
+
if vpb < best:
|
| 253 |
+
best = vpb
|
| 254 |
+
bstate = {k: v.detach().clone() for k, v in m.state_dict().items()}
|
| 255 |
+
bad = 0
|
| 256 |
+
else:
|
| 257 |
+
bad += 1
|
| 258 |
+
if bad >= 15:
|
| 259 |
+
break
|
| 260 |
+
m.load_state_dict(bstate)
|
| 261 |
+
tpb, _ = ev(Xte, Yte)
|
| 262 |
+
bench = {"solar": 0.0285, "wind": 0.0792}[track]
|
| 263 |
+
print(
|
| 264 |
+
f"RESULT {track} pinball {tpb:.5f} (off.bench {bench}) | {npar / 1000.0:.0f}K"
|
| 265 |
+
)
|
| 266 |
+
if save:
|
| 267 |
+
torch.save(
|
| 268 |
+
{
|
| 269 |
+
"state": m.state_dict(),
|
| 270 |
+
"cfg": dict(Fin=Fin, L=L, D=D, modes=modes, nblk=nblk),
|
| 271 |
+
"feats": feats,
|
| 272 |
+
"track": track,
|
| 273 |
+
"test_pinball": tpb,
|
| 274 |
+
"off_bench": bench,
|
| 275 |
+
"npar": npar,
|
| 276 |
+
},
|
| 277 |
+
save,
|
| 278 |
+
)
|
| 279 |
+
print(f"SAVED {save}")
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
if __name__ == "__main__":
|
| 283 |
+
main()
|
verify.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
sys.path.insert(0, os.path.dirname(__file__))
|
| 7 |
+
from modeling import load_model
|
| 8 |
+
|
| 9 |
+
SHAPES = {"solar": (1, 6, 20), "wind": (1, 12, 15)}
|
| 10 |
+
VERIFICATION = {"solar": 1e-06, "wind": 0.446117}
|
| 11 |
+
TOL = 0.001
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def fixed_input(track):
|
| 15 |
+
torch.manual_seed(0)
|
| 16 |
+
return torch.randn(*SHAPES[track])
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def main():
|
| 20 |
+
ap = argparse.ArgumentParser()
|
| 21 |
+
ap.add_argument("--track", choices=["solar", "wind"], required=True)
|
| 22 |
+
ap.add_argument("--weights", default=".")
|
| 23 |
+
args = ap.parse_args()
|
| 24 |
+
model = load_model(args.weights, track=args.track)
|
| 25 |
+
x = fixed_input(args.track)
|
| 26 |
+
with torch.no_grad():
|
| 27 |
+
out = model(x)
|
| 28 |
+
if out.dim() != 2 or out.shape[0] != 1 or out.shape[-1] != 99:
|
| 29 |
+
print(f"Fail: unexpected output shape {tuple(out.shape)}, expected (1, 99)")
|
| 30 |
+
sys.exit(1)
|
| 31 |
+
p50 = out[0, 49].item()
|
| 32 |
+
print(f"Captured first-hour P50 for {args.track}: {p50:.6f}")
|
| 33 |
+
ref = VERIFICATION[args.track]
|
| 34 |
+
if abs(p50 - ref) > TOL:
|
| 35 |
+
print(
|
| 36 |
+
f"Fail: P50 {p50:.6f} differs from verification {ref:.6f} by more than {TOL}"
|
| 37 |
+
)
|
| 38 |
+
sys.exit(1)
|
| 39 |
+
print(f"Verification check OK (P50 within {TOL} of {ref:.6f})")
|
| 40 |
+
sys.exit(0)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
if __name__ == "__main__":
|
| 44 |
+
main()
|
wind.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c0ad80ab6f8722e22deb64c964c75c9289f3e9c98f5a20654e09376963f217f2
|
| 3 |
+
size 1913168
|