{"id":114589,"date":"2026-08-30T00:53:22","date_gmt":"2026-08-30T00:53:22","guid":{"rendered":"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/"},"modified":"2026-08-30T00:53:22","modified_gmt":"2026-08-30T00:53:22","slug":"building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio","status":"publish","type":"post","link":"https:\/\/youzum.net\/es\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/","title":{"rendered":"Building Custom Batched Ensemble Weather Forecasting with NVIDIA Earth2Studio"},"content":{"rendered":"<p class=\"wp-block-paragraph\">In this <a href=\"https:\/\/github.com\/MARKTECHPOST-AI-MEDIA-INC\/AI-Agents-Projects-Tutorials\/blob\/main\/Deep%20Learning\/NVIDIA_Earth2Studio_Custom_Ensemble_Forecasting_Marktechpost.ipynb\" target=\"_blank\" rel=\"noreferrer noopener\">tutorial<\/a>, we build an ensemble weather forecasting workflow with <a href=\"https:\/\/github.com\/NVIDIA\/earth2studio\">NVIDIA Earth2Studio<\/a>. We install the required Earth2Studio components while preserving Colab\u2019s existing CUDA-enabled PyTorch environment, load the FCN prognostic model, and retrieve atmospheric initial conditions from GFS. We then implement a custom wind-power diagnostic that converts 10-meter wind components into turbine capacity factors, along with a variable-scaled perturbation system that applies physically appropriate noise amplitudes to different atmospheric variables while retaining an unperturbed control member. Using Earth2Studio\u2019s low-level iterator, coordinate-mapping, batching, and Zarr APIs, we construct our own ensemble execution pipeline, write forecast and diagnostic fields to a coordinate-aware data store, and verify the forecasts against GFS analyses using latitude-weighted RMSE, fair CRPS, ensemble spread, and spread-skill ratios. Finally, we visualize ensemble uncertainty through spatial maps, geopotential-height spaghetti contours, point-based fan charts, wind-capacity-factor forecasts, and lead-time skill curves.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">import importlib.util, os, subprocess, sys\nif importlib.util.find_spec(\"earth2studio\") is None:\n   import numpy as _np, torch as _torch\n   cfile = os.path.join(os.getcwd(), \"e2s_constraints.txt\")\n   with open(cfile, \"w\") as f:\n       f.write(f\"torch=={_torch.__version__.split('+')[0]}n\")\n       f.write(f\"numpy=={_np.__version__}n\")\n   env = {**os.environ, \"PIP_CONSTRAINT\": cfile}\n   subprocess.check_call(\n       [sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n        \"earth2studio[fcn,data,perturbation,statistics]\"], env=env)\n   print(\"n&gt;&gt;&gt; Install done. If the imports below fail: Runtime &gt; Restart session, re-run.n\")\nos.environ.setdefault(\"EARTH2STUDIO_CACHE\", \"\/content\/e2s_cache\")\nos.makedirs(\"outputs\", exist_ok=True)\nfrom collections import OrderedDict\nfrom datetime import datetime, timedelta, timezone\nfrom tqdm.auto import tqdm\nfrom earth2studio.data import GFS, fetch_data\nfrom earth2studio.io import ZarrBackend\nfrom earth2studio.models.batch import batch_coords, batch_func\nfrom earth2studio.models.px import FCN\nfrom earth2studio.statistics import rmse\nfrom earth2studio.utils import handshake_coords, handshake_dim\nfrom earth2studio.utils.coords import map_coords\nfrom earth2studio.utils.time import to_time_array\nfrom earth2studio.utils.type import CoordSystem\nif DEVICE.type == \"cpu\":\n   print(\"!! No GPU detected \u2014 this will be very slow. Runtime &gt; Change runtime type &gt; T4 GPU\")\nNENSEMBLE  = 8\nBATCH_SIZE = 2\nNSTEPS     = 8\nSAVE_VARS  = [\"t2m\", \"z500\", \"u10m\", \"v10m\", \"tcwv\"]\nVERIFY_VARS = [\"t2m\", \"z500\", \"u10m\"]\nINIT = (datetime.now(timezone.utc) - timedelta(days=7)).replace()\nINIT_STR = INIT.strftime(\"%Y-%m-%dT%H:%M:%S\")\nPOI = (\"New Delhi\", 28.61, 77.21)\nprint(f\"Initialization: {INIT_STR}  |  device: {DEVICE}\")\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We install Earth2Studio while preserving Colab\u2019s existing CUDA-enabled PyTorch and NumPy environment through package constraints. We configure the model cache, import the forecasting, data, statistics, plotting, and coordinate-management utilities, and detect the available compute device. We also define the ensemble size, batch size, forecast duration, saved variables, verification variables, initialization time, and New Delhi point of interest.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">class WindPowerCF(torch.nn.Module):\n   \"\"\"Turbine capacity factor [0,1] from 10 m winds via power-law shear + power curve.\"\"\"\n   def __init__(self, lat, lon, hub=100.0, alpha=0.143,\n                cut_in=3.0, rated=12.0, cut_out=25.0):\n       super().__init__()\n       self.lat, self.lon = lat, lon\n       self.hub, self.alpha = hub, alpha\n       self.cut_in, self.rated, self.cut_out = cut_in, rated, cut_out\n   def input_coords(self) -&gt; CoordSystem:\n       return OrderedDict({\n           \"batch\": np.empty(0),\n           \"variable\": np.array([\"u10m\", \"v10m\"]),\n           \"lat\": self.lat,\n           \"lon\": self.lon,\n       })\n   @batch_coords()\n   def output_coords(self, input_coords: CoordSystem) -&gt; CoordSystem:\n       target = self.input_coords()\n       for i, (key, _) in enumerate(target.items()):\n           if key != \"batch\":\n               handshake_dim(input_coords, key, i)\n               handshake_coords(input_coords, target, key)\n       oc = OrderedDict({\n           \"batch\": np.empty(0),\n           \"variable\": np.array([\"wind_cf\"]),\n           \"lat\": self.lat,\n           \"lon\": self.lon,\n       })\n       oc[\"batch\"] = input_coords[\"batch\"]\n       return oc\n   @batch_func()\n   def __call__(self, x: torch.Tensor, coords: CoordSystem):\n       oc = self.output_coords(coords)\n       u, v = x[..., 0:1, :, :], x[..., 1:2, :, :]\n       ws10 = torch.sqrt(u * u + v * v)\n       ws = ws10 * (self.hub \/ 10.0) ** self.alpha\n       ramp = (ws ** 3 - self.cut_in ** 3) \/ (self.rated ** 3 - self.cut_in ** 3)\n       cf = torch.zeros_like(ws)\n       cf = torch.where((ws &gt;= self.cut_in) &amp; (ws &lt; self.rated), ramp.clamp(0, 1), cf)\n       cf = torch.where((ws &gt;= self.rated) &amp; (ws &lt;= self.cut_out), torch.ones_like(cf), cf)\n       return cf, oc\nclass VariableScaledNoise:\n   \"\"\"Spatially correlated noise with per-variable amplitudes + control member.\"\"\"\n   def __init__(self, amplitudes: dict, default: float = 0.0, control_member: bool = True):\n       self.amplitudes, self.default, self.control = amplitudes, default, control_member\n       try:\n           from earth2studio.perturbation import SphericalGaussian\n           self.sampler, self.kind = SphericalGaussian(noise_amplitude=1.0), \"SphericalGaussian\"\n       except Exception:\n           from earth2studio.perturbation import Brown\n           self.sampler, self.kind = Brown(noise_amplitude=1.0), \"Brown\"\n   def __call__(self, x: torch.Tensor, coords: CoordSystem):\n       noise, _ = self.sampler(torch.zeros_like(x), coords)\n       vax = list(coords).index(\"variable\")\n       amps = torch.tensor([self.amplitudes.get(str(v), self.default)\n                            for v in coords[\"variable\"]], device=x.device, dtype=x.dtype)\n       shape = [1] * x.ndim; shape[vax] = amps.numel()\n       pert = noise * amps.reshape(shape)\n       if self.control and \"ensemble\" in coords:\n           eax = list(coords).index(\"ensemble\")\n           mask = torch.tensor((np.asarray(coords[\"ensemble\"]) != 0).astype(np.float32),\n                               device=x.device, dtype=x.dtype)\n           mshape = [1] * x.ndim; mshape[eax] = mask.numel()\n           pert = pert * mask.reshape(mshape)\n       return x + pert, coords\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We create a custom diagnostic model that converts 10-meter wind components into hub-height wind speed and turbine capacity factor. We validate coordinate compatibility through Earth2Studio\u2019s handshake utilities and support batched inputs with the provided decorators. We also implement variable-specific spatial perturbations that retain member zero as an unperturbed control forecast.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">def write_vars(io, x, coords, names):\n   \"\"\"Write selected channels of a (\u2026, variable, lat, lon) tensor to the IO backend.\"\"\"\n   vax = list(coords).index(\"variable\")\n   sub = OrderedDict((k, v) for k, v in coords.items() if k != \"variable\")\n   for name in names:\n       hit = np.where(np.asarray(coords[\"variable\"]) == name)[0]\n       if hit.size:\n           io.write(x.select(vax, int(hit[0])).cpu(), sub, name)\ndef run_ensemble(time, nsteps, nensemble, batch_size, prognostic, diagnostic,\n                perturbation, data, io, save_vars, device):\n   time = to_time_array(time)\n   ic = prognostic.input_coords()\n   x0, c0 = fetch_data(source=data, time=time, lead_time=ic[\"lead_time\"],\n                       variable=ic[\"variable\"], device=device)\n   print(f\"Initial condition tensor: {tuple(x0.shape)}  dims={list(c0)}\")\n   oc = prognostic.output_coords(ic)\n   dt = oc[\"lead_time\"]\n   prog_vars = [v for v in save_vars if v in set(map(str, oc[\"variable\"]))]\n   total = OrderedDict({\n       \"ensemble\": np.arange(nensemble),\n       \"time\": time,\n       \"lead_time\": np.asarray([dt * i for i in range(nsteps + 1)]).flatten(),\n       \"lat\": oc[\"lat\"],\n       \"lon\": oc[\"lon\"],\n   })\n   io.add_array(total, prog_vars + [\"wind_cf\"])\n   dx_target = OrderedDict((k, v) for k, v in diagnostic.input_coords().items() if k != \"batch\")\n   nbatch = int(np.ceil(nensemble \/ batch_size))\n   with torch.inference_mode():\n       for b in tqdm(range(nbatch), desc=\"ensemble batches\"):\n           lo = b * batch_size\n           n = min(batch_size, nensemble - lo)\n           x = x0.unsqueeze(0).repeat(n, *([1] * x0.ndim))\n           coords = OrderedDict({\"ensemble\": np.arange(lo, lo + n), **c0})\n           x, coords = perturbation(x, coords)\n           x, coords = map_coords(x, coords, ic)\n           for step, (xs, cs) in enumerate(prognostic.create_iterator(x, coords)):\n               write_vars(io, xs, cs, prog_vars)\n               xw, cw = map_coords(xs, cs, dx_target)\n               xw, cw = diagnostic(xw, cw)\n               write_vars(io, xw, cw, [\"wind_cf\"])\n               if step &gt;= nsteps:\n                   break\n           torch.cuda.empty_cache() if device.type == \"cuda\" else None\n   return io\nmodel = FCN.load_model(FCN.load_default_package()).to(DEVICE)\ngrid = model.output_coords(model.input_coords())\nLAT, LON = grid[\"lat\"], grid[\"lon\"]\ndiagnostic = WindPowerCF(LAT, LON).to(DEVICE)\npert = VariableScaledNoise(\n   amplitudes={\"t2m\": 0.20, \"t850\": 0.20, \"z500\": 40.0, \"z850\": 25.0,\n               \"u10m\": 0.25, \"v10m\": 0.25, \"u500\": 0.40, \"v500\": 0.40, \"tcwv\": 0.30},\n   default=0.0, control_member=True)\nprint(f\"Perturbation sampler: {pert.kind}\")\nio = ZarrBackend(file_name=\"outputs\/e2s_ensemble.zarr\",\n                chunks={\"ensemble\": 1, \"time\": 1, \"lead_time\": 1},\n                backend_kwargs={\"overwrite\": True})\nio = run_ensemble([INIT_STR], NSTEPS, NENSEMBLE, BATCH_SIZE,\n                 model, diagnostic, pert, GFS(), io, SAVE_VARS, DEVICE)\nprint(io.root.tree())\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We define helper functions that select atmospheric channels and write them into a coordinate-aware Zarr backend. We build a custom batched ensemble loop that fetches GFS initial conditions, perturbs ensemble members, aligns coordinates, iterates the FCN model, and chains the wind-power diagnostic. We then load the model, initialize the diagnostic and perturbation components, execute the forecast, and inspect the resulting Zarr structure.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">leads = np.asarray(io[\"lead_time\"][:]).astype(\"timedelta64[ns]\")\nlead_h = leads.astype(\"timedelta64[h]\").astype(int)\nvalid = to_time_array([INIT_STR])[0] + leads\ntruth, tc = fetch_data(source=GFS(), time=valid,\n                      lead_time=np.array([np.timedelta64(0, \"h\")]),\n                      variable=np.array(VERIFY_VARS), device=\"cpu\")\ntruth = truth[:, 0]\nw = torch.cos(torch.deg2rad(torch.as_tensor(np.asarray(LAT), dtype=torch.float32)))\nw2d = w[:, None].expand(len(LAT), len(LON)).contiguous()\nmcoords = OrderedDict({\"lead_time\": leads, \"lat\": np.asarray(LAT), \"lon\": np.asarray(LON)})\ndef fair_crps(ens, obs, weights):\n   \"\"\"Fair (unbiased) CRPS, lat-weighted. ens: (M, lat, lon), obs: (lat, lon).\"\"\"\n   M = ens.shape[0]\n   wn = weights \/ weights.sum()\n   skill = ((ens - obs).abs() * wn).sum(dim=(-2, -1)).mean()\n   spread = torch.zeros((), dtype=ens.dtype)\n   for i in range(M):\n       spread = spread + ((ens[i] - ens).abs() * wn).sum(dim=(-2, -1)).sum()\n   return (skill - spread \/ (2 * M * (M - 1))).item()\nscores = {}\nfor k, var in enumerate(VERIFY_VARS):\n   fc = torch.as_tensor(np.asarray(io[var][:]))[:, 0].float()\n   ob = truth[:, k].float()\n   mean = fc.mean(0)\n   try:\n       metric = rmse(reduction_dimensions=[\"lat\", \"lon\"], weights=w2d)\n       r, _ = metric(mean, mcoords, ob, mcoords)\n       r = r.numpy()\n   except Exception as e:\n       print(f\"(built-in rmse unavailable: {e})\")\n       wn = (w2d \/ w2d.sum())\n       r = torch.sqrt((((mean - ob) ** 2) * wn).sum(dim=(-2, -1))).numpy()\n   wn = w2d \/ w2d.sum()\n   spread = torch.sqrt((fc.var(0, unbiased=True) * wn).sum(dim=(-2, -1))).numpy()\n   crps = np.array([fair_crps(fc[:, t], ob[t], w2d) for t in range(fc.shape[1])])\n   scores[var] = dict(rmse=r, spread=spread, crps=crps, fc=fc, obs=ob, mean=mean)\n   print(f\"n=== {var} ===\")\n   print(f\"{'lead[h]':&gt;8}{'RMSE':&gt;12}{'spread':&gt;12}{'ratio':&gt;9}{'CRPS':&gt;12}\")\n   for t in range(len(lead_h)):\n       ratio = spread[t] \/ r[t] if r[t] &gt; 0 else np.nan\n       print(f\"{lead_h[t]:&gt;8}{r[t]:&gt;12.3f}{spread[t]:&gt;12.3f}{ratio:&gt;9.2f}{crps[t]:&gt;12.3f}\")\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We retrieve GFS analyses for every forecast-valid time and use them as the reference data for verification. We calculate latitude-weighted RMSE, ensemble spread, fair CRPS, and spread-to-error ratios for temperature, geopotential height, and wind variables. We store the forecast fields and evaluation metrics in a structured dictionary and print lead-time skill summaries for each variable.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">lat_np, lon_np = np.asarray(LAT), np.asarray(LON)\nilat = int(np.argmin(np.abs(lat_np - POI[1])))\nilon = int(np.argmin(np.abs(lon_np - (POI[2] % 360))))\nlast = -1\nd = scores[\"t2m\"]\nfields = [(d[\"mean\"][last].numpy() - 273.15, \"ensemble mean t2m [C]\", \"RdBu_r\", None),\n         (d[\"fc\"][:, last].std(0).numpy(), \"ensemble spread [K]\", \"magma\", None),\n         (d[\"obs\"][last].numpy() - 273.15, \"GFS analysis [C]\", \"RdBu_r\", None),\n         ((d[\"mean\"][last] - d[\"obs\"][last]).numpy(), \"mean error [K]\", \"coolwarm\", 5)]\nfig, axs = plt.subplots(2, 2, figsize=(15, 7), constrained_layout=True)\nfor ax, (f, title, cmap, lim) in zip(axs.ravel(), fields):\n   kw = dict(vmin=-lim, vmax=lim) if lim else {}\n   im = ax.pcolormesh(lon_np, lat_np, f, cmap=cmap, shading=\"auto\", **kw)\n   ax.set_title(f\"{title} \u2014 +{lead_h[last]} h\"); plt.colorbar(im, ax=ax, shrink=0.85)\nplt.show()\nz = scores[\"z500\"][\"fc\"][:, last].numpy() \/ 9.81\nla = (lat_np &gt; 25) &amp; (lat_np &lt; 75)\nlo = (lon_np &gt; 280) | (lon_np &lt; 40)\nlon_shift = np.where(lon_np &gt; 180, lon_np - 360, lon_np)\norder = np.argsort(lon_shift[lo])\nplt.figure(figsize=(11, 5))\nfor m in range(z.shape[0]):\n   sub = z[m][np.ix_(la, lo)][:, order]\n   plt.contour(lon_shift[lo][order], lat_np[la], sub, levels=[5520],\n               colors=[\"k\" if m == 0 else \"C0\"], linewidths=[2.0 if m == 0 else 0.8])\nzo = scores[\"z500\"][\"obs\"][last].numpy() \/ 9.81\nplt.contour(lon_shift[lo][order], lat_np[la], zo[np.ix_(la, lo)][:, order],\n           levels=[5520], colors=\"crimson\", linewidths=2.5)\nplt.title(f\"z500 5520 m spaghetti at +{lead_h[last]} h \"\n         f\"(black=control, blue=members, red=GFS analysis)\")\nplt.xlabel(\"lon\"); plt.ylabel(\"lat\"); plt.show()\nt2m_pt = scores[\"t2m\"][\"fc\"][:, :, ilat, ilon].numpy() - 273.15\nobs_pt = scores[\"t2m\"][\"obs\"][:, ilat, ilon].numpy() - 273.15\ncf_pt = np.asarray(io[\"wind_cf\"][:])[:, 0, :, ilat, ilon]\nfig, (a1, a2) = plt.subplots(1, 2, figsize=(14, 4))\na1.fill_between(lead_h, t2m_pt.min(0), t2m_pt.max(0), alpha=0.25, label=\"member range\")\na1.plot(lead_h, t2m_pt.mean(0), \"o-\", label=\"ensemble mean\")\na1.plot(lead_h, t2m_pt[0], \"k--\", label=\"control\")\na1.plot(lead_h, obs_pt, \"r^-\", label=\"GFS analysis\")\na1.set_title(f\"2 m temperature \u2014 {POI[0]}\"); a1.set_xlabel(\"lead [h]\"); a1.set_ylabel(\"C\")\na1.legend(); a1.grid(alpha=.3)\na2.fill_between(lead_h, cf_pt.min(0), cf_pt.max(0), alpha=0.25, color=\"seagreen\")\na2.plot(lead_h, cf_pt.mean(0), \"o-\", color=\"seagreen\")\na2.set_title(f\"wind capacity factor (custom diagnostic) \u2014 {POI[0]}\")\na2.set_xlabel(\"lead [h]\"); a2.set_ylim(0, 1); a2.grid(alpha=.3)\nplt.tight_layout(); plt.show()\nfig, axs = plt.subplots(1, len(VERIFY_VARS), figsize=(5 * len(VERIFY_VARS), 3.6))\nfor ax, var in zip(np.atleast_1d(axs), VERIFY_VARS):\n   s = scores[var]\n   ax.plot(lead_h, s[\"rmse\"], \"o-\", label=\"RMSE (ens. mean)\")\n   ax.plot(lead_h, s[\"spread\"], \"s--\", label=\"spread\")\n   ax.plot(lead_h, s[\"crps\"], \"^:\", label=\"fair CRPS\")\n   ax.set_title(var); ax.set_xlabel(\"lead [h]\"); ax.grid(alpha=.3); ax.legend(fontsize=8)\nplt.tight_layout(); plt.show()\nimport xarray as xr\nds = xr.open_zarr(\"outputs\/e2s_ensemble.zarr\")\nprint(ds)\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We visualize ensemble behavior through temperature mean, spread, analysis, and error maps at the final forecast lead time. We generate geopotential-height spaghetti contours, a New Delhi temperature fan chart, a wind-capacity-factor forecast, and lead-time skill curves. We finally open the Zarr output with Xarray so that we can inspect, analyze, or export the complete ensemble dataset.<\/p>\n<p class=\"wp-block-paragraph\">In conclusion, we established a flexible and extensible Earth2Studio workflow that goes beyond running a predefined ensemble function. We directly controlled initial-condition perturbation, member batching, model iteration, diagnostic chaining, coordinate alignment, data persistence, verification, and visualization within a single Colab environment. We also demonstrated how physically scaled perturbations and an unperturbed control member help us interpret ensemble spread. At the same time, RMSE, fair CRPS, and spread-skill diagnostics allow us to evaluate forecast accuracy and calibration across lead times. The resulting Zarr dataset preserves the complete ensemble structure and remains accessible through Xarray for further analysis or conversion. Because the workflow follows Earth2Studio\u2019s component interfaces, we can extend it by replacing the prognostic model, changing the atmospheric data source, adding new diagnostics, increasing the ensemble size, or adopting asynchronous storage without redesigning the full forecasting pipeline.<\/p>\n<p class=\"wp-block-paragraph\">\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n<\/p><p class=\"wp-block-paragraph\">\n<\/p><p class=\"wp-block-paragraph\">Check out the\u00a0<strong><a href=\"https:\/\/github.com\/MARKTECHPOST-AI-MEDIA-INC\/AI-Agents-Projects-Tutorials\/blob\/main\/Deep%20Learning\/NVIDIA_Earth2Studio_Custom_Ensemble_Forecasting_Marktechpost.ipynb\" target=\"_blank\" rel=\"noreferrer noopener\">FULL CODES here<\/a>.<\/strong>\u00a0Also,\u00a0feel free to follow us on\u00a0<strong><a href=\"https:\/\/x.com\/intent\/follow?screen_name=marktechpost\" target=\"_blank\" rel=\"noopener\"><mark>Twitter<\/mark><\/a><\/strong>\u00a0and don\u2019t forget to join our\u00a0<strong><a href=\"https:\/\/www.reddit.com\/r\/machinelearningnews\/\" target=\"_blank\" rel=\"noopener\">150k+ML SubReddit<\/a><\/strong>\u00a0and Subscribe to\u00a0<strong><a href=\"https:\/\/magic.beehiiv.com\/v1\/f5e63dd4-5653-4f09-83e2-321a8b1ba526?email=%7B%7Bemail%7D%7D\" target=\"_blank\" rel=\"noopener\">our Newsletter<\/a><\/strong>. Wait! are you on telegram?\u00a0<strong><a href=\"https:\/\/t.me\/machinelearningresearchnews\" target=\"_blank\" rel=\"noopener\">now you can join us on telegram as well.<\/a><\/strong><\/p>\n<p class=\"wp-block-paragraph\">Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.?\u00a0<strong><a href=\"https:\/\/forms.gle\/wbash1wF6efRj8G58\" target=\"_blank\" rel=\"noopener\"><mark>Connect with us<\/mark><\/a><\/strong><\/p>\n<p>The post <a href=\"https:\/\/www.marktechpost.com\/2026\/08\/29\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/\">Building Custom Batched Ensemble Weather Forecasting with NVIDIA Earth2Studio<\/a> appeared first on <a href=\"https:\/\/www.marktechpost.com\/\">MarkTechPost<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>In this tutorial, we build an ensemble weather forecasting workflow with NVIDIA Earth2Studio. We install the required Earth2Studio components while preserving Colab\u2019s existing CUDA-enabled PyTorch environment, load the FCN prognostic model, and retrieve atmospheric initial conditions from GFS. We then implement a custom wind-power diagnostic that converts 10-meter wind components into turbine capacity factors, along with a variable-scaled perturbation system that applies physically appropriate noise amplitudes to different atmospheric variables while retaining an unperturbed control member. Using Earth2Studio\u2019s low-level iterator, coordinate-mapping, batching, and Zarr APIs, we construct our own ensemble execution pipeline, write forecast and diagnostic fields to a coordinate-aware data store, and verify the forecasts against GFS analyses using latitude-weighted RMSE, fair CRPS, ensemble spread, and spread-skill ratios. Finally, we visualize ensemble uncertainty through spatial maps, geopotential-height spaghetti contours, point-based fan charts, wind-capacity-factor forecasts, and lead-time skill curves. Copy CodeCopiedUse a different Browser import importlib.util, os, subprocess, sys if importlib.util.find_spec(&#8220;earth2studio&#8221;) is None: import numpy as _np, torch as _torch cfile = os.path.join(os.getcwd(), &#8220;e2s_constraints.txt&#8221;) with open(cfile, &#8220;w&#8221;) as f: f.write(f&#8221;torch=={_torch.__version__.split(&#8216;+&#8217;)[0]}n&#8221;) f.write(f&#8221;numpy=={_np.__version__}n&#8221;) env = {**os.environ, &#8220;PIP_CONSTRAINT&#8221;: cfile} subprocess.check_call( [sys.executable, &#8220;-m&#8221;, &#8220;pip&#8221;, &#8220;install&#8221;, &#8220;-q&#8221;, &#8220;earth2studio[fcn,data,perturbation,statistics]&#8221;], env=env) print(&#8220;n&gt;&gt;&gt; Install done. If the imports below fail: Runtime &gt; Restart session, re-run.n&#8221;) os.environ.setdefault(&#8220;EARTH2STUDIO_CACHE&#8221;, &#8220;\/content\/e2s_cache&#8221;) os.makedirs(&#8220;outputs&#8221;, exist_ok=True) from collections import OrderedDict from datetime import datetime, timedelta, timezone from tqdm.auto import tqdm from earth2studio.data import GFS, fetch_data from earth2studio.io import ZarrBackend from earth2studio.models.batch import batch_coords, batch_func from earth2studio.models.px import FCN from earth2studio.statistics import rmse from earth2studio.utils import handshake_coords, handshake_dim from earth2studio.utils.coords import map_coords from earth2studio.utils.time import to_time_array from earth2studio.utils.type import CoordSystem if DEVICE.type == &#8220;cpu&#8221;: print(&#8220;!! No GPU detected \u2014 this will be very slow. Runtime &gt; Change runtime type &gt; T4 GPU&#8221;) NENSEMBLE = 8 BATCH_SIZE = 2 NSTEPS = 8 SAVE_VARS = [&#8220;t2m&#8221;, &#8220;z500&#8221;, &#8220;u10m&#8221;, &#8220;v10m&#8221;, &#8220;tcwv&#8221;] VERIFY_VARS = [&#8220;t2m&#8221;, &#8220;z500&#8221;, &#8220;u10m&#8221;] INIT = (datetime.now(timezone.utc) &#8211; timedelta(days=7)).replace() INIT_STR = INIT.strftime(&#8220;%Y-%m-%dT%H:%M:%S&#8221;) POI = (&#8220;New Delhi&#8221;, 28.61, 77.21) print(f&#8221;Initialization: {INIT_STR} | device: {DEVICE}&#8221;) We install Earth2Studio while preserving Colab\u2019s existing CUDA-enabled PyTorch and NumPy environment through package constraints. We configure the model cache, import the forecasting, data, statistics, plotting, and coordinate-management utilities, and detect the available compute device. We also define the ensemble size, batch size, forecast duration, saved variables, verification variables, initialization time, and New Delhi point of interest. Copy CodeCopiedUse a different Browser class WindPowerCF(torch.nn.Module): &#8220;&#8221;&#8221;Turbine capacity factor [0,1] from 10 m winds via power-law shear + power curve.&#8221;&#8221;&#8221; def __init__(self, lat, lon, hub=100.0, alpha=0.143, cut_in=3.0, rated=12.0, cut_out=25.0): super().__init__() self.lat, self.lon = lat, lon self.hub, self.alpha = hub, alpha self.cut_in, self.rated, self.cut_out = cut_in, rated, cut_out def input_coords(self) -&gt; CoordSystem: return OrderedDict({ &#8220;batch&#8221;: np.empty(0), &#8220;variable&#8221;: np.array([&#8220;u10m&#8221;, &#8220;v10m&#8221;]), &#8220;lat&#8221;: self.lat, &#8220;lon&#8221;: self.lon, }) @batch_coords() def output_coords(self, input_coords: CoordSystem) -&gt; CoordSystem: target = self.input_coords() for i, (key, _) in enumerate(target.items()): if key != &#8220;batch&#8221;: handshake_dim(input_coords, key, i) handshake_coords(input_coords, target, key) oc = OrderedDict({ &#8220;batch&#8221;: np.empty(0), &#8220;variable&#8221;: np.array([&#8220;wind_cf&#8221;]), &#8220;lat&#8221;: self.lat, &#8220;lon&#8221;: self.lon, }) oc[&#8220;batch&#8221;] = input_coords[&#8220;batch&#8221;] return oc @batch_func() def __call__(self, x: torch.Tensor, coords: CoordSystem): oc = self.output_coords(coords) u, v = x[&#8230;, 0:1, :, :], x[&#8230;, 1:2, :, :] ws10 = torch.sqrt(u * u + v * v) ws = ws10 * (self.hub \/ 10.0) ** self.alpha ramp = (ws ** 3 &#8211; self.cut_in ** 3) \/ (self.rated ** 3 &#8211; self.cut_in ** 3) cf = torch.zeros_like(ws) cf = torch.where((ws &gt;= self.cut_in) &amp; (ws &lt; self.rated), ramp.clamp(0, 1), cf) cf = torch.where((ws &gt;= self.rated) &amp; (ws &lt;= self.cut_out), torch.ones_like(cf), cf) return cf, oc class VariableScaledNoise: &#8220;&#8221;&#8221;Spatially correlated noise with per-variable amplitudes + control member.&#8221;&#8221;&#8221; def __init__(self, amplitudes: dict, default: float = 0.0, control_member: bool = True): self.amplitudes, self.default, self.control = amplitudes, default, control_member try: from earth2studio.perturbation import SphericalGaussian self.sampler, self.kind = SphericalGaussian(noise_amplitude=1.0), &#8220;SphericalGaussian&#8221; except Exception: from earth2studio.perturbation import Brown self.sampler, self.kind = Brown(noise_amplitude=1.0), &#8220;Brown&#8221; def __call__(self, x: torch.Tensor, coords: CoordSystem): noise, _ = self.sampler(torch.zeros_like(x), coords) vax = list(coords).index(&#8220;variable&#8221;) amps = torch.tensor([self.amplitudes.get(str(v), self.default) for v in coords[&#8220;variable&#8221;]], device=x.device, dtype=x.dtype) shape = [1] * x.ndim; shape[vax] = amps.numel() pert = noise * amps.reshape(shape) if self.control and &#8220;ensemble&#8221; in coords: eax = list(coords).index(&#8220;ensemble&#8221;) mask = torch.tensor((np.asarray(coords[&#8220;ensemble&#8221;]) != 0).astype(np.float32), device=x.device, dtype=x.dtype) mshape = [1] * x.ndim; mshape[eax] = mask.numel() pert = pert * mask.reshape(mshape) return x + pert, coords We create a custom diagnostic model that converts 10-meter wind components into hub-height wind speed and turbine capacity factor. We validate coordinate compatibility through Earth2Studio\u2019s handshake utilities and support batched inputs with the provided decorators. We also implement variable-specific spatial perturbations that retain member zero as an unperturbed control forecast. Copy CodeCopiedUse a different Browser def write_vars(io, x, coords, names): &#8220;&#8221;&#8221;Write selected channels of a (\u2026, variable, lat, lon) tensor to the IO backend.&#8221;&#8221;&#8221; vax = list(coords).index(&#8220;variable&#8221;) sub = OrderedDict((k, v) for k, v in coords.items() if k != &#8220;variable&#8221;) for name in names: hit = np.where(np.asarray(coords[&#8220;variable&#8221;]) == name)[0] if hit.size: io.write(x.select(vax, int(hit[0])).cpu(), sub, name) def run_ensemble(time, nsteps, nensemble, batch_size, prognostic, diagnostic, perturbation, data, io, save_vars, device): time = to_time_array(time) ic = prognostic.input_coords() x0, c0 = fetch_data(source=data, time=time, lead_time=ic[&#8220;lead_time&#8221;], variable=ic[&#8220;variable&#8221;], device=device) print(f&#8221;Initial condition tensor: {tuple(x0.shape)} dims={list(c0)}&#8221;) oc = prognostic.output_coords(ic) dt = oc[&#8220;lead_time&#8221;] prog_vars = [v for v in save_vars if v in set(map(str, oc[&#8220;variable&#8221;]))] total = OrderedDict({ &#8220;ensemble&#8221;: np.arange(nensemble), &#8220;time&#8221;: time, &#8220;lead_time&#8221;: np.asarray([dt * i for i in range(nsteps + 1)]).flatten(), &#8220;lat&#8221;: oc[&#8220;lat&#8221;], &#8220;lon&#8221;: oc[&#8220;lon&#8221;], }) io.add_array(total, prog_vars + [&#8220;wind_cf&#8221;]) dx_target = OrderedDict((k, v) for k, v in diagnostic.input_coords().items() if k != &#8220;batch&#8221;) nbatch = int(np.ceil(nensemble \/ batch_size)) with torch.inference_mode(): for b in tqdm(range(nbatch), desc=&#8221;ensemble batches&#8221;): lo = b * batch_size n = min(batch_size, nensemble &#8211; lo) x = x0.unsqueeze(0).repeat(n, *([1] * x0.ndim)) coords = OrderedDict({&#8220;ensemble&#8221;: np.arange(lo, lo + n), **c0}) x, coords = perturbation(x, coords) x, coords = map_coords(x, coords, ic) for step, (xs, cs) in enumerate(prognostic.create_iterator(x, coords)): write_vars(io, xs, cs, prog_vars) xw, cw = map_coords(xs, cs, dx_target) xw, cw = diagnostic(xw, cw) write_vars(io, xw, cw, [&#8220;wind_cf&#8221;]) if step &gt;= nsteps: break torch.cuda.empty_cache() if device.type == &#8220;cuda&#8221; else None return io model = FCN.load_model(FCN.load_default_package()).to(DEVICE) grid = model.output_coords(model.input_coords()) LAT, LON = grid[&#8220;lat&#8221;], grid[&#8220;lon&#8221;] diagnostic = WindPowerCF(LAT, LON).to(DEVICE) pert = VariableScaledNoise( amplitudes={&#8220;t2m&#8221;: 0.20, &#8220;t850&#8221;: 0.20, &#8220;z500&#8221;: 40.0, &#8220;z850&#8221;: 25.0, &#8220;u10m&#8221;: 0.25, &#8220;v10m&#8221;: 0.25,<\/p>","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"pmpro_default_level":"","site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"_pvb_checkbox_block_on_post":false,"footnotes":""},"categories":[52,5,7,1],"tags":[],"class_list":["post-114589","post","type-post","status-publish","format-standard","hentry","category-ai-club","category-committee","category-news","category-uncategorized","pmpro-has-access"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v25.3 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Building Custom Batched Ensemble Weather Forecasting with NVIDIA Earth2Studio - YouZum<\/title>\n<meta name=\"description\" content=\"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/youzum.net\/es\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/\" \/>\n<meta property=\"og:locale\" content=\"es_ES\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building Custom Batched Ensemble Weather Forecasting with NVIDIA Earth2Studio - YouZum\" \/>\n<meta property=\"og:description\" content=\"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19\" \/>\n<meta property=\"og:url\" content=\"https:\/\/youzum.net\/es\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/\" \/>\n<meta property=\"og:site_name\" content=\"YouZum\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/DroneAssociationTH\/\" \/>\n<meta property=\"article:published_time\" content=\"2026-08-30T00:53:22+00:00\" \/>\n<meta name=\"author\" content=\"admin NU\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Escrito por\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin NU\" \/>\n\t<meta name=\"twitter:label2\" content=\"Tiempo de lectura\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minutos\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/\"},\"author\":{\"name\":\"admin NU\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c\"},\"headline\":\"Building Custom Batched Ensemble Weather Forecasting with NVIDIA Earth2Studio\",\"datePublished\":\"2026-08-30T00:53:22+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/\"},\"wordCount\":694,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#organization\"},\"articleSection\":[\"AI\",\"Committee\",\"News\",\"Uncategorized\"],\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/\",\"url\":\"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/\",\"name\":\"Building Custom Batched Ensemble Weather Forecasting with NVIDIA Earth2Studio - YouZum\",\"isPartOf\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#website\"},\"datePublished\":\"2026-08-30T00:53:22+00:00\",\"description\":\"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19\",\"breadcrumb\":{\"@id\":\"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/#breadcrumb\"},\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/youzum.net\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building Custom Batched Ensemble Weather Forecasting with NVIDIA Earth2Studio\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/yousum.gpucore.co\/#website\",\"url\":\"https:\/\/yousum.gpucore.co\/\",\"name\":\"YouSum\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/yousum.gpucore.co\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"es\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/yousum.gpucore.co\/#organization\",\"name\":\"Drone Association Thailand\",\"url\":\"https:\/\/yousum.gpucore.co\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png\",\"contentUrl\":\"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png\",\"width\":300,\"height\":300,\"caption\":\"Drone Association Thailand\"},\"image\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/DroneAssociationTH\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c\",\"name\":\"admin NU\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png\",\"contentUrl\":\"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png\",\"caption\":\"admin NU\"},\"url\":\"https:\/\/youzum.net\/es\/members\/adminnu\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Building Custom Batched Ensemble Weather Forecasting with NVIDIA Earth2Studio - YouZum","description":"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/youzum.net\/es\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/","og_locale":"es_ES","og_type":"article","og_title":"Building Custom Batched Ensemble Weather Forecasting with NVIDIA Earth2Studio - YouZum","og_description":"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19","og_url":"https:\/\/youzum.net\/es\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/","og_site_name":"YouZum","article_publisher":"https:\/\/www.facebook.com\/DroneAssociationTH\/","article_published_time":"2026-08-30T00:53:22+00:00","author":"admin NU","twitter_card":"summary_large_image","twitter_misc":{"Escrito por":"admin NU","Tiempo de lectura":"13 minutos"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/#article","isPartOf":{"@id":"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/"},"author":{"name":"admin NU","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c"},"headline":"Building Custom Batched Ensemble Weather Forecasting with NVIDIA Earth2Studio","datePublished":"2026-08-30T00:53:22+00:00","mainEntityOfPage":{"@id":"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/"},"wordCount":694,"commentCount":0,"publisher":{"@id":"https:\/\/yousum.gpucore.co\/#organization"},"articleSection":["AI","Committee","News","Uncategorized"],"inLanguage":"es","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/","url":"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/","name":"Building Custom Batched Ensemble Weather Forecasting with NVIDIA Earth2Studio - YouZum","isPartOf":{"@id":"https:\/\/yousum.gpucore.co\/#website"},"datePublished":"2026-08-30T00:53:22+00:00","description":"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19","breadcrumb":{"@id":"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/#breadcrumb"},"inLanguage":"es","potentialAction":[{"@type":"ReadAction","target":["https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/youzum.net\/building-custom-batched-ensemble-weather-forecasting-with-nvidia-earth2studio\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/youzum.net\/"},{"@type":"ListItem","position":2,"name":"Building Custom Batched Ensemble Weather Forecasting with NVIDIA Earth2Studio"}]},{"@type":"WebSite","@id":"https:\/\/yousum.gpucore.co\/#website","url":"https:\/\/yousum.gpucore.co\/","name":"YouSum","description":"","publisher":{"@id":"https:\/\/yousum.gpucore.co\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/yousum.gpucore.co\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"es"},{"@type":"Organization","@id":"https:\/\/yousum.gpucore.co\/#organization","name":"Drone Association Thailand","url":"https:\/\/yousum.gpucore.co\/","logo":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/","url":"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png","contentUrl":"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png","width":300,"height":300,"caption":"Drone Association Thailand"},"image":{"@id":"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/DroneAssociationTH\/"]},{"@type":"Person","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c","name":"admin NU","image":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/person\/image\/","url":"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png","contentUrl":"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png","caption":"admin NU"},"url":"https:\/\/youzum.net\/es\/members\/adminnu\/"}]}},"rttpg_featured_image_url":null,"rttpg_author":{"display_name":"admin NU","author_link":"https:\/\/youzum.net\/es\/members\/adminnu\/"},"rttpg_comment":0,"rttpg_category":"<a href=\"https:\/\/youzum.net\/es\/category\/ai-club\/\" rel=\"category tag\">AI<\/a> <a href=\"https:\/\/youzum.net\/es\/category\/committee\/\" rel=\"category tag\">Committee<\/a> <a href=\"https:\/\/youzum.net\/es\/category\/news\/\" rel=\"category tag\">News<\/a> <a href=\"https:\/\/youzum.net\/es\/category\/uncategorized\/\" rel=\"category tag\">Uncategorized<\/a>","rttpg_excerpt":"In this tutorial, we build an ensemble weather forecasting workflow with NVIDIA Earth2Studio. We install the required Earth2Studio components while preserving Colab\u2019s existing CUDA-enabled PyTorch environment, load the FCN prognostic model, and retrieve atmospheric initial conditions from GFS. We then implement a custom wind-power diagnostic that converts 10-meter wind components into turbine capacity factors, along&hellip;","_links":{"self":[{"href":"https:\/\/youzum.net\/es\/wp-json\/wp\/v2\/posts\/114589","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/youzum.net\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/youzum.net\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/youzum.net\/es\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/youzum.net\/es\/wp-json\/wp\/v2\/comments?post=114589"}],"version-history":[{"count":0,"href":"https:\/\/youzum.net\/es\/wp-json\/wp\/v2\/posts\/114589\/revisions"}],"wp:attachment":[{"href":"https:\/\/youzum.net\/es\/wp-json\/wp\/v2\/media?parent=114589"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/youzum.net\/es\/wp-json\/wp\/v2\/categories?post=114589"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/youzum.net\/es\/wp-json\/wp\/v2\/tags?post=114589"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}