WeatherSynth.Core 0.1.0

dotnet add package WeatherSynth.Core --version 0.1.0
                    
NuGet\Install-Package WeatherSynth.Core -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="WeatherSynth.Core" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="WeatherSynth.Core" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="WeatherSynth.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add WeatherSynth.Core --version 0.1.0
                    
#r "nuget: WeatherSynth.Core, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package WeatherSynth.Core@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=WeatherSynth.Core&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=WeatherSynth.Core&version=0.1.0
                    
Install as a Cake Tool

WeatherSynth

A package to create synthetic weather data.

The data itself is realistic, as it is derived from the Clear Sky Index and historical data.

Currently, the data used to create the synthetic data is from Bochum, which is in western Germany. This data is required without it, the project doesn't work.

Why Bochum? Because it is the best station with data that I could find from the DWD. So if your location differs wildly from Bochum, I would change the underlying data.

This produces synthetic weather, not a forecast. Every year returned is one statistically plausible realisation for the site, reproducible from its seed never a prediction of what a specific real-world date will actually do.

Installation

dotnet add package WeatherSynth

That pulls in WeatherSynth.Core (the physics) automatically

Quick start

No files, no fitting: SyntheticWeather.Default bundles a model already fitted from the Bochum (solar) and Essen-Bredeney (wind) records as a few dozen coefficients, and generates from that.

using WeatherSynth;

CoupledWeatherYear year = SyntheticWeather.Default.GenerateYear(2027, seed: 4242);

foreach (var day in year.Days)
    Console.WriteLine($"{day.Date} {day.Solar.GhiKWhPerM2:F2} kWh/m²  {day.Wind.MeanSpeed:F1} m/s");

SyntheticWeather.DefaultSolar and .DefaultWind give you just one resource each, if you don't need both. Everything below this point covers fitting from your own DWD station record instead of the bundled one.

Usage

SyntheticSolarProvider is the entry point. Fit once from the station record, then ask it for as many years as you need:

var provider = SyntheticSolarProvider.FromDwdRecord("data/dwd_bochum_solar.csv", DwdStations.Bochum);

// A year at the fitting station, reproducible from (year, seed).
SyntheticSolarYear year = provider.GenerateYear(2026, seed: 42);

foreach (var day in year.Days)
    Console.WriteLine($"{day.Date:yyyy-MM-dd} {day.GhiKWhPerM2:F3} kWh/m²");

Console.WriteLine($"{year.GhiKWhPerM2:F0} kWh/m² over {year.Days.Count} days");

// Somewhere else: the distributions transfer, the geometry does not, so pass the target site.
var köln = new SolarSite(51.02095, 6.89422, altitudeMeters: 50.0);
var elsewhere = provider.GenerateYear(2026, seed: 42, köln);

Fitting is the expensive step it reads the whole record so keep the provider around and call GenerateYear per request. The provider is immutable and thread-safe; the generators it hands out are not. provider.Generate(start, end, seed) covers spans that are not calendar years.

Each year is one realisation, never a forecast: the same seed reproduces it exactly, a different seed is an equally plausible year for that site.

The repo has 3 folders:

  1. tests: tests (AI-generated by Claude Opus 5)
  2. src: the logic itself (mostly handwritten)
  3. samples: things to try out (AI-generated by Claude Opus 5). Run with: dotnet run --project samples/WeatherSynth.Sample -c Release -- <command>
    • summary: coverage, gaps, monthly daily-GHI totals, clear-day counts
    • kt: builds the clearness-index dataset and runs the acceptance checks
    • calibrate: fits Linke turbidity against measured cloudless days
    • fit: fits the monthly Beta distributions and scores them (KS, persistence)
    • year: prints one synthetic year at daily resolution year [year] [seed]
    • viz: writes viz/index.html, a self-contained page with a solar/wind/combined switch. viz <years> [seed] [coupled|independent] projects future years instead of the record's span
    • couple: fits the solar-wind coupling and scores it (needs both records)
    • zenith: solar position vs. the DWD ZENIT column (151k reference angles)
    • decompose: splits the zenith residual into declination vs. hour-angle error
    • impact: what the zenith residual costs on daily clear-sky GHI
    • fitcoords: recovers station coordinates from ZENIT by residual minimisation
    • sanity: the original clear-sky harness (equinox/solstice totals)
    • windsummary: wind coverage, gaps, monthly mean speeds, the cube-law correction
    • windfit: fits the twelve monthly Weibull distributions and scores them (KS, persistence)
    • windyear: prints one synthetic wind year at daily resolution windyear [year] [seed]
    • windpower: turbine yield, checked against the record's own hourly energy

It is built for .NET 9.0.

It supports Global Radiation, which can be used for PV generation calculations, and daily mean wind speed. SyntheticWindProvider is the wind entry point and works like the solar one:

var wind = SyntheticWindProvider.FromDwdRecord("data/dwd_essen_wind.csv", DwdWindStations.EssenBredeney);
SyntheticWindYear year = wind.GenerateYear(2026, seed: 42);

Console.WriteLine($"{year.MeanSpeed:F2} m/s mean, windiest day {year.MaxSpeed:F2} m/s");

// Somewhere higher up. Read the warning below before trusting the result.
var hub = new WindSite(HeightMeters: 100.0, RoughnessLengthMeters: 0.1);
var lifted = wind.GenerateYear(2026, seed: 42, hub);

Two warnings about wind output. Wind power goes as the cube of speed, so a daily mean speed is not enough for an energy estimate it is low by about 25% at this station. Use MeanCubedSpeed, which is carried for exactly that reason. And the height transfer is a big source of error: the log law and the power law disagree by 26% over a 15 m → 100 m extrapolation. Generating at the station's own 15 m applies no transfer at all.

For an actual turbine, MeanCubedSpeed is still not enough that's why there is a fake power curve in TurbinePowerCurve, which TurbineYield integrates over each day to return a daily energy. It takes 3 as the cut-in speed, 12.5 as the rated speed, and 25 as the cut-out speed. With 2 MW rated power, it returns the daily energy in kWh. It is a very rough estimate, but it is better than nothing. Keep in mind that the values are just examples fit them to your own needs.

Coupling the two

Normally a day can't be both sunny and windy, so the two resources are not independent. The CoupledWeatherProvider takes this into account and generates days that pair up the way real ones do. It needs both records, because it has to measure the correlation between them. That makes it a bit more realistic. It is an opt-in feature.

var both = CoupledWeatherProvider.FromDwdRecords(
    "data/dwd_bochum_solar.csv", DwdStations.Bochum,
    "data/dwd_essen_wind.csv", DwdWindStations.EssenBredeney);

CoupledWeatherYear year = both.GenerateYear(2026, seed: 42);
Console.WriteLine($"{year.Solar.GhiKWhPerM2:F0} kWh/m², {year.Wind.MeanSpeed:F2} m/s");

Two stations

Solar is fitted at Bochum (DWD 7365) and wind at Essen-Bredeney (DWD 01303), about 29 km apart. That split is forced rather than chosen: Bochum carries no wind record at all. Both sit at roughly 150 m in the same regional weather, which is what makes the pairing defensible but any coupling measured between the two resources is attenuated by the separation, and so is a lower bound on the co-located value.

The wind record runs 2009-01-01 to 2025-12-31 at hourly resolution, matching the solar record's span. Its anemometer is at 15 m above ground, not the 10 m almost everyone assumes.

Data attribution

The weather data in data/dwd_bochum_solar.csv comes from the Deutscher Wetterdienst DWD Climate Data Center and is used under the CC BY 4.0 license.

The wind data in data/dwd_essen_wind.csv comes from the same source under the same CC BY 4.0 license. It is DWD's hourly wind product for station 01303, spliced from the historical/ and recent/ archives with the overlap de-duplicated in favour of historical/, and restricted to 2009-2025.

© Deutscher Wetterdienst, 2026

License

The code in this repository is licensed under the GNU AFFERO GENERAL PUBLIC LICENSE. This does not cover the DWD data, which is licensed separately as noted above.

WeatherSynth.Core depends on SolarCalculator (LGPL-3.0) for solar position primitives, pulled in as an ordinary NuGet package reference. Its license is included alongside this one wherever WeatherSynth is distributed.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on WeatherSynth.Core:

Package Downloads
WeatherSynth

A stochastic weather generator: synthetic, physically-plausible daily solar irradiance and wind speed for a site. Two Possible options either use the bundled default model with zero setup, or fit your own from a DWD station record.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 61 8/23/2026