loadcast 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- loadcast-0.1.1/LICENSE +21 -0
- loadcast-0.1.1/PKG-INFO +204 -0
- loadcast-0.1.1/README.md +168 -0
- loadcast-0.1.1/pyproject.toml +61 -0
- loadcast-0.1.1/setup.cfg +4 -0
- loadcast-0.1.1/src/loadcast/__init__.py +94 -0
- loadcast-0.1.1/src/loadcast/classes.py +187 -0
- loadcast-0.1.1/src/loadcast/data/__init__.py +5 -0
- loadcast-0.1.1/src/loadcast/data/process_classes.json +242 -0
- loadcast-0.1.1/src/loadcast/data/reference.json +158 -0
- loadcast-0.1.1/src/loadcast/fitting.py +303 -0
- loadcast-0.1.1/src/loadcast/generator.py +406 -0
- loadcast-0.1.1/src/loadcast/reference.py +175 -0
- loadcast-0.1.1/src/loadcast/spec.py +243 -0
- loadcast-0.1.1/src/loadcast/stats.py +126 -0
- loadcast-0.1.1/src/loadcast.egg-info/PKG-INFO +204 -0
- loadcast-0.1.1/src/loadcast.egg-info/SOURCES.txt +24 -0
- loadcast-0.1.1/src/loadcast.egg-info/dependency_links.txt +1 -0
- loadcast-0.1.1/src/loadcast.egg-info/requires.txt +16 -0
- loadcast-0.1.1/src/loadcast.egg-info/top_level.txt +1 -0
- loadcast-0.1.1/tests/test_exactness.py +73 -0
- loadcast-0.1.1/tests/test_fitting.py +121 -0
- loadcast-0.1.1/tests/test_identities.py +203 -0
- loadcast-0.1.1/tests/test_reference.py +69 -0
- loadcast-0.1.1/tests/test_resolution.py +192 -0
- loadcast-0.1.1/tests/test_spec.py +73 -0
loadcast-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mattéo Hauglustaine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
loadcast-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: loadcast
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Synthetic multi-carrier industrial energy demand profiles with controllable cross-carrier structure
|
|
5
|
+
Author-email: Matteo Hauglustaine <matteo@abeo.be>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Matt-haug/loadcast
|
|
8
|
+
Project-URL: Documentation, https://loadcast.readthedocs.io
|
|
9
|
+
Project-URL: Repository, https://github.com/Matt-haug/loadcast
|
|
10
|
+
Project-URL: Issues, https://github.com/Matt-haug/loadcast/issues
|
|
11
|
+
Keywords: energy,demand,load profile,industrial,synthetic data,time series,heat,cooling,multi-carrier
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: numpy>=1.21
|
|
24
|
+
Requires-Dist: pandas>=1.3
|
|
25
|
+
Provides-Extra: plot
|
|
26
|
+
Requires-Dist: matplotlib>=3.5; extra == "plot"
|
|
27
|
+
Provides-Extra: test
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
29
|
+
Provides-Extra: docs
|
|
30
|
+
Requires-Dist: mkdocs>=1.5; extra == "docs"
|
|
31
|
+
Requires-Dist: mkdocs-material>=9.0; extra == "docs"
|
|
32
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: loadcast[docs,plot,test]; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
[](https://pypi.org/project/loadcast/)
|
|
38
|
+
[](https://loadcast.readthedocs.io/en/latest/)
|
|
39
|
+
[](https://github.com/Matt-haug/loadcast/actions/workflows/tests.yml)
|
|
40
|
+
[](https://github.com/Matt-haug/loadcast/blob/main/LICENSE)
|
|
41
|
+
[](https://doi.org/10.5281/zenodo.22961300)
|
|
42
|
+
|
|
43
|
+
# loadcast
|
|
44
|
+
|
|
45
|
+
**A synthetic multi-carrier industrial energy demand generator**
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import loadcast
|
|
49
|
+
|
|
50
|
+
profiles = loadcast.generate(
|
|
51
|
+
annual_mwh=10_000,
|
|
52
|
+
heat_to_cold=7.4, # this site wants 7.4x more heat than cold
|
|
53
|
+
elec_share=0.20, # a fifth of its energy is electricity
|
|
54
|
+
process_class="semi_continuous", # weekday shifts, weekend trough
|
|
55
|
+
carrier_correlation=0.75, # how tightly the carriers move together
|
|
56
|
+
carrier_lag_h={"cold": 6}, # cold demand trails heat by six hours
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Returns an 8760-row hourly or quarter-hourly frame of `heat_kw`, `cold_kw` and `elec_kw`.
|
|
61
|
+
|
|
62
|
+

|
|
63
|
+
|
|
64
|
+
*Left: every carrier as a fixed multiple of one shared profile, the construction behind published
|
|
65
|
+
disaggregated data, which is correlated at 1 by algebra. Right: `loadcast` with `carrier_correlation=0.60`.
|
|
66
|
+
More in the [tutorial notebook](https://github.com/Matt-haug/loadcast/blob/main/examples/01_tutorial.ipynb).*
|
|
67
|
+
|
|
68
|
+
## Why
|
|
69
|
+
|
|
70
|
+
Designing anything with storage against an industrial site needs to know not
|
|
71
|
+
just *how much* of each carrier the site wants, but **whether it wants them at
|
|
72
|
+
the same hours**. A store exists to bridge the gap when it does not.
|
|
73
|
+
|
|
74
|
+
Existing sources cannot answer that question:
|
|
75
|
+
|
|
76
|
+
| source | approach | industrial | coincidence controllable? |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| demandlib / BDEW SLP | deterministic step profiles | yes | no — deterministic |
|
|
79
|
+
| RAMP | bottom-up from appliances | no | no — emerges from user schedules |
|
|
80
|
+
| LoadProfileGenerator | behavioural simulation | no | no |
|
|
81
|
+
| EnTiSe | framework, pluggable methods | partly | no — pipeline dependency |
|
|
82
|
+
| JERICHO-E-usage | disaggregated national totals | yes | no — fixed at 1.000 |
|
|
83
|
+
|
|
84
|
+
The disaggregated datasets build every carrier as a fixed scalar times one
|
|
85
|
+
shared profile, $P_m(t) = \kappa_m p(t)$, under which the correlation between
|
|
86
|
+
any two carriers is identically 1 — an artefact of the construction, not a
|
|
87
|
+
measurement. Feeding that into a storage study builds the answer into the
|
|
88
|
+
question.
|
|
89
|
+
|
|
90
|
+
`loadcast` makes the relationship an input you set and sweep.
|
|
91
|
+
|
|
92
|
+
## What you specify
|
|
93
|
+
|
|
94
|
+
Things you can answer from a site visit or an energy bill, not things the
|
|
95
|
+
mathematics wants:
|
|
96
|
+
|
|
97
|
+
| input | meaning |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `annual_mwh` | total annual energy across all carriers |
|
|
100
|
+
| `heat_to_cold` | ratio of annual heat to annual cold |
|
|
101
|
+
| `elec_share` | electricity as a fraction of the total |
|
|
102
|
+
| `process_class` | continuous, semi-continuous, campaign or batch |
|
|
103
|
+
| `carrier_correlation` | how tightly carriers move together, at peak alignment |
|
|
104
|
+
| `carrier_lag_h` | hours by which a carrier trails the others |
|
|
105
|
+
|
|
106
|
+
The four process classes are calibrated on metered industrial sites. If none
|
|
107
|
+
fits yours, build one from your own statistics:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from loadcast import class_from_observations, register_class
|
|
111
|
+
|
|
112
|
+
mine = class_from_observations(
|
|
113
|
+
"my_plant", load_factor=0.45, cv=0.60, lag1=0.90,
|
|
114
|
+
calendar_from="semi_continuous",
|
|
115
|
+
)
|
|
116
|
+
register_class(mine)
|
|
117
|
+
profiles = loadcast.generate(process_class="my_plant", annual_mwh=5_000)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The inversion from observables to generator parameters is closed form. See [mathematics](https://loadcast.readthedocs.io/en/latest/mathematics/) for details.
|
|
121
|
+
|
|
122
|
+
## What it guarantees exactly
|
|
123
|
+
|
|
124
|
+
Not approximately, but to floating-point:
|
|
125
|
+
|
|
126
|
+
- annual energy equals `annual_mwh`
|
|
127
|
+
- the heat:cold ratio equals `heat_to_cold`
|
|
128
|
+
- the electricity share equals `elec_share`
|
|
129
|
+
- the load factor equals the process class's, via the capacity cap fixed point
|
|
130
|
+
- every value is strictly positive
|
|
131
|
+
|
|
132
|
+
## Diagnostics
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
loadcast.summary_table(profiles) # the four scalars
|
|
136
|
+
loadcast.duration_curve(profiles["heat_kw"]) # load duration curve
|
|
137
|
+
loadcast.cross_correlogram(profiles["heat_kw"], profiles["cold_kw"])
|
|
138
|
+
loadcast.integrated_drift(profiles["heat_kw"], profiles["cold_kw"], window_h=24)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Read the correlogram's **peak position**, not only its height. A lag and a weak
|
|
142
|
+
coupling are indistinguishable from the contemporaneous correlation alone: as
|
|
143
|
+
carriers are offset, the lag-0 correlation decays towards the floor the shared
|
|
144
|
+
calendar imposes, exactly where a small correlation would also put it.
|
|
145
|
+
|
|
146
|
+
`integrated_drift` is the quantity a store actually responds to. A buffer does
|
|
147
|
+
not sample a mismatch, it integrates one, so what sizes it is the area under the
|
|
148
|
+
mismatch autocovariance over the storage window — correlation is a single point
|
|
149
|
+
of that picture.
|
|
150
|
+
|
|
151
|
+
## What it deliberately does not do
|
|
152
|
+
|
|
153
|
+
- **No temperature.** It carries energy by carrier. Whether a machine can
|
|
154
|
+
*reach* a given process is a separate question, answered from the machine's
|
|
155
|
+
own limits.
|
|
156
|
+
- **No claim about your site.** Parameters are yours to choose; defaults are
|
|
157
|
+
indicative values with a stated provenance, not predictions. Results are meant
|
|
158
|
+
to be reported as they hold across a swept space, not at one assumed point.
|
|
159
|
+
- **No cold validation.** No site in the calibration archive meters
|
|
160
|
+
refrigeration. The cold carrier's annual quantity is whatever you specify;
|
|
161
|
+
its *shape* rests on the same calendar and correlation assumptions as the
|
|
162
|
+
others, and nothing independent confirms it. Every cold result carries this.
|
|
163
|
+
|
|
164
|
+
## Install
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
pip install loadcast
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Requires Python 3.9+, numpy and pandas. Nothing else.
|
|
171
|
+
|
|
172
|
+
## Documentation
|
|
173
|
+
|
|
174
|
+
A detailed documentation of the library is available at: https://loadcast.readthedocs.io/en/latest/
|
|
175
|
+
|
|
176
|
+
Refer to that documentation for usage instructions and examples and details of the method.
|
|
177
|
+
|
|
178
|
+
## Calibration data
|
|
179
|
+
|
|
180
|
+
The process classes are calibrated on metered industrial sites that cannot be
|
|
181
|
+
redistributed. What ships is the *derived* statistics and calendar factors —
|
|
182
|
+
group means carrying no identifying information. The raw series are not part of
|
|
183
|
+
this repository.
|
|
184
|
+
|
|
185
|
+
## Contributing
|
|
186
|
+
|
|
187
|
+
Issues and pull requests are welcome — see [CONTRIBUTING.md](https://github.com/Matt-haug/loadcast/blob/main/CONTRIBUTING.md)
|
|
188
|
+
for scope, what a test looks like here, and support expectations. Reports that
|
|
189
|
+
the generated profiles disagree with a real site you have measured are
|
|
190
|
+
especially welcome: the calibration archive is small, and that is the package's
|
|
191
|
+
main limitation.
|
|
192
|
+
|
|
193
|
+
This project follows the [Contributor Covenant](https://github.com/Matt-haug/loadcast/blob/main/CODE_OF_CONDUCT.md).
|
|
194
|
+
Generative AI was used in building it; see [AI_USAGE.md](https://github.com/Matt-haug/loadcast/blob/main/AI_USAGE.md).
|
|
195
|
+
|
|
196
|
+
## Citing
|
|
197
|
+
|
|
198
|
+
Use the "Cite this repository" button, or `CITATION.cff`. Each release is
|
|
199
|
+
archived on Zenodo with a DOI.
|
|
200
|
+
|
|
201
|
+
## Licence
|
|
202
|
+
|
|
203
|
+
MIT — see [LICENSE](https://github.com/Matt-haug/loadcast/blob/main/LICENSE). Changes are recorded in
|
|
204
|
+
[CHANGELOG.md](https://github.com/Matt-haug/loadcast/blob/main/CHANGELOG.md).
|
loadcast-0.1.1/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
[](https://pypi.org/project/loadcast/)
|
|
2
|
+
[](https://loadcast.readthedocs.io/en/latest/)
|
|
3
|
+
[](https://github.com/Matt-haug/loadcast/actions/workflows/tests.yml)
|
|
4
|
+
[](https://github.com/Matt-haug/loadcast/blob/main/LICENSE)
|
|
5
|
+
[](https://doi.org/10.5281/zenodo.22961300)
|
|
6
|
+
|
|
7
|
+
# loadcast
|
|
8
|
+
|
|
9
|
+
**A synthetic multi-carrier industrial energy demand generator**
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
import loadcast
|
|
13
|
+
|
|
14
|
+
profiles = loadcast.generate(
|
|
15
|
+
annual_mwh=10_000,
|
|
16
|
+
heat_to_cold=7.4, # this site wants 7.4x more heat than cold
|
|
17
|
+
elec_share=0.20, # a fifth of its energy is electricity
|
|
18
|
+
process_class="semi_continuous", # weekday shifts, weekend trough
|
|
19
|
+
carrier_correlation=0.75, # how tightly the carriers move together
|
|
20
|
+
carrier_lag_h={"cold": 6}, # cold demand trails heat by six hours
|
|
21
|
+
)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Returns an 8760-row hourly or quarter-hourly frame of `heat_kw`, `cold_kw` and `elec_kw`.
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
*Left: every carrier as a fixed multiple of one shared profile, the construction behind published
|
|
29
|
+
disaggregated data, which is correlated at 1 by algebra. Right: `loadcast` with `carrier_correlation=0.60`.
|
|
30
|
+
More in the [tutorial notebook](https://github.com/Matt-haug/loadcast/blob/main/examples/01_tutorial.ipynb).*
|
|
31
|
+
|
|
32
|
+
## Why
|
|
33
|
+
|
|
34
|
+
Designing anything with storage against an industrial site needs to know not
|
|
35
|
+
just *how much* of each carrier the site wants, but **whether it wants them at
|
|
36
|
+
the same hours**. A store exists to bridge the gap when it does not.
|
|
37
|
+
|
|
38
|
+
Existing sources cannot answer that question:
|
|
39
|
+
|
|
40
|
+
| source | approach | industrial | coincidence controllable? |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| demandlib / BDEW SLP | deterministic step profiles | yes | no — deterministic |
|
|
43
|
+
| RAMP | bottom-up from appliances | no | no — emerges from user schedules |
|
|
44
|
+
| LoadProfileGenerator | behavioural simulation | no | no |
|
|
45
|
+
| EnTiSe | framework, pluggable methods | partly | no — pipeline dependency |
|
|
46
|
+
| JERICHO-E-usage | disaggregated national totals | yes | no — fixed at 1.000 |
|
|
47
|
+
|
|
48
|
+
The disaggregated datasets build every carrier as a fixed scalar times one
|
|
49
|
+
shared profile, $P_m(t) = \kappa_m p(t)$, under which the correlation between
|
|
50
|
+
any two carriers is identically 1 — an artefact of the construction, not a
|
|
51
|
+
measurement. Feeding that into a storage study builds the answer into the
|
|
52
|
+
question.
|
|
53
|
+
|
|
54
|
+
`loadcast` makes the relationship an input you set and sweep.
|
|
55
|
+
|
|
56
|
+
## What you specify
|
|
57
|
+
|
|
58
|
+
Things you can answer from a site visit or an energy bill, not things the
|
|
59
|
+
mathematics wants:
|
|
60
|
+
|
|
61
|
+
| input | meaning |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `annual_mwh` | total annual energy across all carriers |
|
|
64
|
+
| `heat_to_cold` | ratio of annual heat to annual cold |
|
|
65
|
+
| `elec_share` | electricity as a fraction of the total |
|
|
66
|
+
| `process_class` | continuous, semi-continuous, campaign or batch |
|
|
67
|
+
| `carrier_correlation` | how tightly carriers move together, at peak alignment |
|
|
68
|
+
| `carrier_lag_h` | hours by which a carrier trails the others |
|
|
69
|
+
|
|
70
|
+
The four process classes are calibrated on metered industrial sites. If none
|
|
71
|
+
fits yours, build one from your own statistics:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from loadcast import class_from_observations, register_class
|
|
75
|
+
|
|
76
|
+
mine = class_from_observations(
|
|
77
|
+
"my_plant", load_factor=0.45, cv=0.60, lag1=0.90,
|
|
78
|
+
calendar_from="semi_continuous",
|
|
79
|
+
)
|
|
80
|
+
register_class(mine)
|
|
81
|
+
profiles = loadcast.generate(process_class="my_plant", annual_mwh=5_000)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The inversion from observables to generator parameters is closed form. See [mathematics](https://loadcast.readthedocs.io/en/latest/mathematics/) for details.
|
|
85
|
+
|
|
86
|
+
## What it guarantees exactly
|
|
87
|
+
|
|
88
|
+
Not approximately, but to floating-point:
|
|
89
|
+
|
|
90
|
+
- annual energy equals `annual_mwh`
|
|
91
|
+
- the heat:cold ratio equals `heat_to_cold`
|
|
92
|
+
- the electricity share equals `elec_share`
|
|
93
|
+
- the load factor equals the process class's, via the capacity cap fixed point
|
|
94
|
+
- every value is strictly positive
|
|
95
|
+
|
|
96
|
+
## Diagnostics
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
loadcast.summary_table(profiles) # the four scalars
|
|
100
|
+
loadcast.duration_curve(profiles["heat_kw"]) # load duration curve
|
|
101
|
+
loadcast.cross_correlogram(profiles["heat_kw"], profiles["cold_kw"])
|
|
102
|
+
loadcast.integrated_drift(profiles["heat_kw"], profiles["cold_kw"], window_h=24)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Read the correlogram's **peak position**, not only its height. A lag and a weak
|
|
106
|
+
coupling are indistinguishable from the contemporaneous correlation alone: as
|
|
107
|
+
carriers are offset, the lag-0 correlation decays towards the floor the shared
|
|
108
|
+
calendar imposes, exactly where a small correlation would also put it.
|
|
109
|
+
|
|
110
|
+
`integrated_drift` is the quantity a store actually responds to. A buffer does
|
|
111
|
+
not sample a mismatch, it integrates one, so what sizes it is the area under the
|
|
112
|
+
mismatch autocovariance over the storage window — correlation is a single point
|
|
113
|
+
of that picture.
|
|
114
|
+
|
|
115
|
+
## What it deliberately does not do
|
|
116
|
+
|
|
117
|
+
- **No temperature.** It carries energy by carrier. Whether a machine can
|
|
118
|
+
*reach* a given process is a separate question, answered from the machine's
|
|
119
|
+
own limits.
|
|
120
|
+
- **No claim about your site.** Parameters are yours to choose; defaults are
|
|
121
|
+
indicative values with a stated provenance, not predictions. Results are meant
|
|
122
|
+
to be reported as they hold across a swept space, not at one assumed point.
|
|
123
|
+
- **No cold validation.** No site in the calibration archive meters
|
|
124
|
+
refrigeration. The cold carrier's annual quantity is whatever you specify;
|
|
125
|
+
its *shape* rests on the same calendar and correlation assumptions as the
|
|
126
|
+
others, and nothing independent confirms it. Every cold result carries this.
|
|
127
|
+
|
|
128
|
+
## Install
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pip install loadcast
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Requires Python 3.9+, numpy and pandas. Nothing else.
|
|
135
|
+
|
|
136
|
+
## Documentation
|
|
137
|
+
|
|
138
|
+
A detailed documentation of the library is available at: https://loadcast.readthedocs.io/en/latest/
|
|
139
|
+
|
|
140
|
+
Refer to that documentation for usage instructions and examples and details of the method.
|
|
141
|
+
|
|
142
|
+
## Calibration data
|
|
143
|
+
|
|
144
|
+
The process classes are calibrated on metered industrial sites that cannot be
|
|
145
|
+
redistributed. What ships is the *derived* statistics and calendar factors —
|
|
146
|
+
group means carrying no identifying information. The raw series are not part of
|
|
147
|
+
this repository.
|
|
148
|
+
|
|
149
|
+
## Contributing
|
|
150
|
+
|
|
151
|
+
Issues and pull requests are welcome — see [CONTRIBUTING.md](https://github.com/Matt-haug/loadcast/blob/main/CONTRIBUTING.md)
|
|
152
|
+
for scope, what a test looks like here, and support expectations. Reports that
|
|
153
|
+
the generated profiles disagree with a real site you have measured are
|
|
154
|
+
especially welcome: the calibration archive is small, and that is the package's
|
|
155
|
+
main limitation.
|
|
156
|
+
|
|
157
|
+
This project follows the [Contributor Covenant](https://github.com/Matt-haug/loadcast/blob/main/CODE_OF_CONDUCT.md).
|
|
158
|
+
Generative AI was used in building it; see [AI_USAGE.md](https://github.com/Matt-haug/loadcast/blob/main/AI_USAGE.md).
|
|
159
|
+
|
|
160
|
+
## Citing
|
|
161
|
+
|
|
162
|
+
Use the "Cite this repository" button, or `CITATION.cff`. Each release is
|
|
163
|
+
archived on Zenodo with a DOI.
|
|
164
|
+
|
|
165
|
+
## Licence
|
|
166
|
+
|
|
167
|
+
MIT — see [LICENSE](https://github.com/Matt-haug/loadcast/blob/main/LICENSE). Changes are recorded in
|
|
168
|
+
[CHANGELOG.md](https://github.com/Matt-haug/loadcast/blob/main/CHANGELOG.md).
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "loadcast"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Synthetic multi-carrier industrial energy demand profiles with controllable cross-carrier structure"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Matteo Hauglustaine", email = "matteo@abeo.be" },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"energy",
|
|
17
|
+
"demand",
|
|
18
|
+
"load profile",
|
|
19
|
+
"industrial",
|
|
20
|
+
"synthetic data",
|
|
21
|
+
"time series",
|
|
22
|
+
"heat",
|
|
23
|
+
"cooling",
|
|
24
|
+
"multi-carrier",
|
|
25
|
+
]
|
|
26
|
+
classifiers = [
|
|
27
|
+
"Development Status :: 4 - Beta",
|
|
28
|
+
"Intended Audience :: Science/Research",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3.9",
|
|
31
|
+
"Programming Language :: Python :: 3.10",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Topic :: Scientific/Engineering",
|
|
35
|
+
]
|
|
36
|
+
dependencies = [
|
|
37
|
+
"numpy>=1.21",
|
|
38
|
+
"pandas>=1.3",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
plot = ["matplotlib>=3.5"]
|
|
43
|
+
test = ["pytest>=7.0"]
|
|
44
|
+
docs = ["mkdocs>=1.5", "mkdocs-material>=9.0", "mkdocstrings[python]>=0.24"]
|
|
45
|
+
dev = ["loadcast[plot,test,docs]"]
|
|
46
|
+
|
|
47
|
+
[project.urls]
|
|
48
|
+
Homepage = "https://github.com/Matt-haug/loadcast"
|
|
49
|
+
Documentation = "https://loadcast.readthedocs.io"
|
|
50
|
+
Repository = "https://github.com/Matt-haug/loadcast"
|
|
51
|
+
Issues = "https://github.com/Matt-haug/loadcast/issues"
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.packages.find]
|
|
54
|
+
where = ["src"]
|
|
55
|
+
|
|
56
|
+
[tool.setuptools.package-data]
|
|
57
|
+
loadcast = ["data/*.json"]
|
|
58
|
+
|
|
59
|
+
[tool.pytest.ini_options]
|
|
60
|
+
testpaths = ["tests"]
|
|
61
|
+
addopts = "-q"
|
loadcast-0.1.1/setup.cfg
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""loadcast -- synthetic multi-carrier industrial energy demand.
|
|
2
|
+
|
|
3
|
+
Hourly heat, cold and electricity demand for an industrial site, from the
|
|
4
|
+
handful of numbers someone actually knows about a plant: how much energy it uses
|
|
5
|
+
in a year, roughly how much more heat than cold it wants, how much of the
|
|
6
|
+
total is electricity, and whether the process runs continuously, in shifts or in
|
|
7
|
+
batches.
|
|
8
|
+
|
|
9
|
+
import loadcast
|
|
10
|
+
|
|
11
|
+
profiles = loadcast.generate(
|
|
12
|
+
annual_mwh=10_000,
|
|
13
|
+
heat_to_cold=7.4,
|
|
14
|
+
elec_share=0.20,
|
|
15
|
+
process_class="semi_continuous",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
What makes it different
|
|
19
|
+
-----------------------
|
|
20
|
+
Existing tools either produce deterministic profiles or leave the relationship
|
|
21
|
+
between carriers to fall out of whatever drives them. Both give carriers that
|
|
22
|
+
move in lockstep, which is an artefact of the construction rather than a
|
|
23
|
+
property of industry -- and for anything with storage, *whether the carriers
|
|
24
|
+
peak together* is precisely what decides how much buffering is needed.
|
|
25
|
+
|
|
26
|
+
loadcast makes that relationship an explicit input. `carrier_correlation` sets
|
|
27
|
+
how tightly the carriers move together and `carrier_lag_h` sets how far apart
|
|
28
|
+
they are offset, so a study can sweep coincidence rather than inherit it.
|
|
29
|
+
|
|
30
|
+
What it does not do
|
|
31
|
+
-------------------
|
|
32
|
+
It carries energy by carrier, not temperature: whether a machine can *reach* a
|
|
33
|
+
given process is a separate question, answered from the machine's own limits.
|
|
34
|
+
And the parameters are yours to choose. Defaults are indicative values with a
|
|
35
|
+
stated provenance, not predictions about any real site -- results are meant to
|
|
36
|
+
be reported as they hold across a swept space, not at a single assumed point.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
from .classes import (
|
|
42
|
+
ProcessClass,
|
|
43
|
+
available_classes,
|
|
44
|
+
get_class,
|
|
45
|
+
load_classes,
|
|
46
|
+
register_class,
|
|
47
|
+
)
|
|
48
|
+
from .fitting import (
|
|
49
|
+
bias_report,
|
|
50
|
+
class_from_observations,
|
|
51
|
+
correlation_floor,
|
|
52
|
+
correlation_transfer,
|
|
53
|
+
phi_from_lag1,
|
|
54
|
+
sigma_from_cv,
|
|
55
|
+
)
|
|
56
|
+
from . import reference
|
|
57
|
+
from .generator import (
|
|
58
|
+
build_profiles,
|
|
59
|
+
generate,
|
|
60
|
+
rescale_persistence,
|
|
61
|
+
solve_correlation_knob,
|
|
62
|
+
step_hours,
|
|
63
|
+
)
|
|
64
|
+
from .spec import CARRIERS, DemandSpec
|
|
65
|
+
from .stats import (
|
|
66
|
+
cross_correlogram,
|
|
67
|
+
duration_curve,
|
|
68
|
+
integrated_drift,
|
|
69
|
+
profile_statistics,
|
|
70
|
+
summary_table,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
__version__ = "0.1.1"
|
|
74
|
+
|
|
75
|
+
__all__ = [
|
|
76
|
+
"__version__",
|
|
77
|
+
# what to generate
|
|
78
|
+
"DemandSpec", "CARRIERS",
|
|
79
|
+
# generating
|
|
80
|
+
"generate", "build_profiles", "solve_correlation_knob",
|
|
81
|
+
"step_hours", "rescale_persistence",
|
|
82
|
+
# process classes
|
|
83
|
+
"ProcessClass", "available_classes", "get_class", "load_classes",
|
|
84
|
+
"register_class",
|
|
85
|
+
# fitting to your own measurements
|
|
86
|
+
"class_from_observations", "bias_report",
|
|
87
|
+
"sigma_from_cv", "phi_from_lag1",
|
|
88
|
+
"correlation_floor", "correlation_transfer",
|
|
89
|
+
# diagnostics
|
|
90
|
+
# indicative published values
|
|
91
|
+
"reference",
|
|
92
|
+
"profile_statistics", "summary_table", "duration_curve",
|
|
93
|
+
"cross_correlogram", "integrated_drift",
|
|
94
|
+
]
|