voxcity.simulator_gpu.solar.sky =============================== .. py:module:: voxcity.simulator_gpu.solar.sky .. autoapi-nested-parse:: Sky Discretization Methods for Cumulative Solar Irradiance Calculation. This module provides various methods for dividing the sky hemisphere into patches to improve efficiency of cumulative solar irradiance calculations. Instead of tracing rays for each hourly sun position, sun positions can be binned into sky patches and rays traced once per patch. Supported methods: - Tregenza: 145 patches (standard in Radiance, EnergyPlus, DAYSIM) - Reinhart: Tregenza × MF² patches (high-resolution, used in DAYSIM/Honeybee) - Uniform Grid: Regular azimuth × elevation grid - Fibonacci: Quasi-uniform distribution using golden angle spiral This approach significantly reduces computation time for annual simulations: - 8760 hourly timesteps → ~145-2305 ray traces (30-60× speedup) - Each patch accumulates radiation from multiple sun positions - Patch solid angles weight the contributions correctly References: - Tregenza, P.R. (1987). "Subdivision of the sky hemisphere for luminance measurements." Lighting Research & Technology, 19(1), 13-14. - Reinhart, C.F. & Walkenhorst, O. (2001). "Validation of dynamic RADIANCE-based daylight simulations for a test office with external blinds." Energy and Buildings, 33(7), 683-697. Attributes ---------- .. autoapisummary:: voxcity.simulator_gpu.solar.sky.TREGENZA_BANDS voxcity.simulator_gpu.solar.sky.TREGENZA_BAND_BOUNDARIES voxcity.simulator_gpu.solar.sky.TREGENZA_PATCH_COUNTS voxcity.simulator_gpu.solar.sky.get_tregenza_patch_index_fast Classes ------- .. toctree:: :hidden: /autoapi/voxcity/simulator_gpu/solar/sky/SkyPatches /autoapi/voxcity/simulator_gpu/solar/sky/BinnedSolarData .. autoapisummary:: voxcity.simulator_gpu.solar.sky.SkyPatches voxcity.simulator_gpu.solar.sky.BinnedSolarData Functions --------- .. autoapisummary:: voxcity.simulator_gpu.solar.sky.generate_tregenza_patches voxcity.simulator_gpu.solar.sky.get_tregenza_patch_index voxcity.simulator_gpu.solar.sky.generate_reinhart_patches voxcity.simulator_gpu.solar.sky.generate_uniform_grid_patches voxcity.simulator_gpu.solar.sky.generate_fibonacci_patches voxcity.simulator_gpu.solar.sky.generate_sky_patches voxcity.simulator_gpu.solar.sky.bin_sun_positions_to_patches voxcity.simulator_gpu.solar.sky.get_patch_info voxcity.simulator_gpu.solar.sky.calculate_cumulative_irradiance_weights voxcity.simulator_gpu.solar.sky.visualize_sky_patches voxcity.simulator_gpu.solar.sky.bin_sun_positions_to_tregenza_fast voxcity.simulator_gpu.solar.sky.visualize_binned_radiation Module Contents --------------- .. py:data:: TREGENZA_BANDS :value: [(6.0, 30), (18.0, 30), (30.0, 24), (42.0, 24), (54.0, 18), (66.0, 12), (78.0, 6), (90.0, 1)] .. py:data:: TREGENZA_BAND_BOUNDARIES .. py:data:: TREGENZA_PATCH_COUNTS .. py:function:: generate_tregenza_patches() -> SkyPatches Generate the 145 Tregenza sky patch center directions. The Tregenza subdivision divides the sky hemisphere into 145 patches arranged in 8 altitude bands. This is the standard sky discretization used in Radiance (genskyvec), EnergyPlus, DAYSIM, and Ladybug Tools. :returns: SkyPatches object with patch data .. rubric:: Example >>> patches = generate_tregenza_patches() >>> print(f"Number of patches: {patches.n_patches}") # 145 >>> print(f"Total solid angle: {patches.solid_angles.sum():.4f}") # ~2π .. py:function:: get_tregenza_patch_index(azimuth_deg: float, elevation_deg: float) -> int Get the Tregenza patch index for a given sun position. Numba-accelerated for fast binning of many sun positions. :param azimuth_deg: Solar azimuth in degrees (0-360, 0=North, clockwise) :param elevation_deg: Solar elevation in degrees (0-90) :returns: Patch index (0-144), or -1 if below horizon .. py:function:: generate_reinhart_patches(mf: int = 4) -> SkyPatches Generate Reinhart sky patches (subdivided Tregenza). The Reinhart subdivision increases resolution by subdividing each Tregenza band by a multiplication factor (MF). This allows higher accuracy for detailed solar studies. :param mf: Multiplication factor. Common values: - MF=1: 145 patches (same as Tregenza) - MF=2: 577 patches - MF=4: 2305 patches (common for annual daylight simulation) - MF=6: 5185 patches :returns: SkyPatches object with patch data .. rubric:: Example >>> patches = generate_reinhart_patches(mf=4) >>> print(f"Number of patches: {patches.n_patches}") # ~2305 .. rubric:: References Reinhart, C.F. & Walkenhorst, O. (2001). Energy and Buildings. .. py:function:: generate_uniform_grid_patches(n_azimuth: int = 36, n_elevation: int = 9) -> SkyPatches Generate uniform grid sky patches. Simple subdivision with equal azimuth and elevation spacing. Note: This creates non-equal solid angle patches (smaller near zenith). Useful when uniform angular sampling is preferred over uniform area. :param n_azimuth: Number of azimuth divisions (default: 36 = 10° spacing) :param n_elevation: Number of elevation divisions (default: 9 = 10° spacing) :returns: SkyPatches object with patch data .. rubric:: Example >>> patches = generate_uniform_grid_patches(36, 9) >>> print(f"Number of patches: {patches.n_patches}") # 324 .. py:function:: generate_fibonacci_patches(n_patches: int = 145) -> SkyPatches Generate quasi-uniform sky patches using Fibonacci spiral. Uses the golden angle spiral to distribute points nearly uniformly on the hemisphere. This provides more uniform patch areas than regular grids with fewer total patches. :param n_patches: Number of patches to generate (default: 145 to match Tregenza) :returns: SkyPatches object with patch data .. rubric:: Example >>> patches = generate_fibonacci_patches(200) >>> # Check uniformity: solid angles should be equal >>> print(f"Solid angle std: {patches.solid_angles.std():.6f}") # ~0 .. py:function:: generate_sky_patches(method: str = 'tregenza', **kwargs) -> SkyPatches Generate sky patches using specified discretization method. This is the main entry point for sky discretization. It dispatches to the appropriate method-specific function. :param method: Discretization method: - "tregenza": 145 patches (standard, fast) - "reinhart": Tregenza × MF² (high-resolution) - "uniform": Regular grid (simple) - "fibonacci": Quasi-uniform spiral (balanced) :param \*\*kwargs: Method-specific parameters: - mf: Multiplication factor for Reinhart (default: 4) - n_azimuth, n_elevation: Grid size for uniform - n_patches: Number of patches for Fibonacci :returns: SkyPatches object with patch data .. rubric:: Example >>> # Standard Tregenza >>> patches = generate_sky_patches("tregenza") >>> # High-resolution Reinhart >>> patches = generate_sky_patches("reinhart", mf=4) >>> # Custom uniform grid >>> patches = generate_sky_patches("uniform", n_azimuth=72, n_elevation=18) .. py:function:: bin_sun_positions_to_patches(azimuth_arr: numpy.ndarray, elevation_arr: numpy.ndarray, dni_arr: numpy.ndarray, dhi_arr: Optional[numpy.ndarray] = None, method: str = 'tregenza', **kwargs) -> BinnedSolarData Bin hourly sun positions into sky patches and aggregate radiation. This is the key optimization for cumulative solar irradiance: instead of tracing rays for every hourly sun position, aggregate radiation values for each sky patch and trace rays once per patch. The DNI values are summed for each patch where the sun appears. The DHI values are distributed isotropically across all patches. :param azimuth_arr: Array of solar azimuth values in degrees (0=North) :param elevation_arr: Array of solar elevation values in degrees :param dni_arr: Array of Direct Normal Irradiance values (W/m² or Wh/m²) :param dhi_arr: Array of Diffuse Horizontal Irradiance values (optional) :param method: Sky discretization method :param \*\*kwargs: Additional parameters for patch generation :returns: BinnedSolarData with accumulated radiation per patch .. rubric:: Example >>> from palm_solar.epw import prepare_cumulative_simulation_input >>> az, el, dni, dhi, loc = prepare_cumulative_simulation_input("weather.epw") >>> binned = bin_sun_positions_to_patches(az, el, dni, dhi) >>> print(f"Active patches: {(binned.hours_per_patch > 0).sum()}") .. py:function:: get_patch_info(method: str = 'tregenza', **kwargs) -> dict Get information about a sky discretization method. :param method: Sky discretization method :param \*\*kwargs: Method-specific parameters :returns: Dictionary with method details .. rubric:: Example >>> info = get_patch_info("reinhart", mf=4) >>> print(f"{info['method']}: {info['n_patches']} patches") .. py:function:: calculate_cumulative_irradiance_weights(binned_data: BinnedSolarData, include_diffuse: bool = True) -> Tuple[numpy.ndarray, numpy.ndarray] Calculate patch weights for cumulative irradiance simulation. Returns weights that can be used with ray tracing results to compute cumulative irradiance. The direct component uses binned DNI values, and the diffuse component is distributed isotropically. :param binned_data: BinnedSolarData from bin_sun_positions_to_patches :param include_diffuse: Whether to include diffuse component :returns: - direct_weights: DNI weight per patch (Wh/m²) - diffuse_weights: DHI weight per patch (Wh/m²) :rtype: Tuple of .. rubric:: Example >>> binned = bin_sun_positions_to_patches(az, el, dni, dhi) >>> direct_w, diffuse_w = calculate_cumulative_irradiance_weights(binned) >>> # Use with ray tracing: >>> # cumulative_irradiance = sum(visibility * direct_w + svf * diffuse_w) .. py:function:: visualize_sky_patches(method: str = 'tregenza', ax=None, show: bool = True, **kwargs) Visualize sky patches on a polar plot. :param method: Sky discretization method :param show: Whether to call plt.show() :param \*\*kwargs: Method-specific parameters :returns: matplotlib axis object .. py:data:: get_tregenza_patch_index_fast .. py:function:: bin_sun_positions_to_tregenza_fast(azimuth_arr: numpy.ndarray, elevation_arr: numpy.ndarray, dni_arr: numpy.ndarray) -> Tuple[numpy.ndarray, numpy.ndarray] Numba-accelerated binning of sun positions to Tregenza patches. This function matches the signature of voxcity.simulator.solar.sky.bin_sun_positions_to_tregenza_fast. :param azimuth_arr: Array of solar azimuth values in degrees :param elevation_arr: Array of solar elevation values in degrees :param dni_arr: Array of Direct Normal Irradiance values (W/m²) :returns: - cumulative_dni: shape (145,) - Cumulative DNI (W·h/m²) for each Tregenza patch - hours_count: shape (145,) - Number of hours with sun in each patch :rtype: Tuple of (cumulative_dni, hours_count) arrays .. py:function:: visualize_binned_radiation(binned_data: BinnedSolarData, show: bool = True) Visualize binned solar radiation on a polar plot. :param binned_data: BinnedSolarData from bin_sun_positions_to_patches :param show: Whether to call plt.show() :returns: matplotlib axis object