voxcity.simulator.solar.sky =========================== .. py:module:: voxcity.simulator.solar.sky .. autoapi-nested-parse:: Sky Discretization Methods for Solar Simulation. 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 Attributes ---------- .. autoapisummary:: voxcity.simulator.solar.sky.TREGENZA_BANDS voxcity.simulator.solar.sky.TREGENZA_BAND_BOUNDARIES Functions --------- .. autoapisummary:: voxcity.simulator.solar.sky.generate_tregenza_patches voxcity.simulator.solar.sky.get_tregenza_patch_index voxcity.simulator.solar.sky.get_tregenza_patch_index_fast voxcity.simulator.solar.sky.generate_reinhart_patches voxcity.simulator.solar.sky.generate_uniform_grid_patches voxcity.simulator.solar.sky.generate_fibonacci_patches voxcity.simulator.solar.sky.bin_sun_positions_to_patches voxcity.simulator.solar.sky.bin_sun_positions_to_tregenza_fast voxcity.simulator.solar.sky.get_patch_info voxcity.simulator.solar.sky.visualize_sky_patches 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 :value: [0.0, 12.0, 24.0, 36.0, 48.0, 60.0, 72.0, 84.0, 90.0] .. py:function:: generate_tregenza_patches() 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: * **patches** (*np.ndarray, shape (145, 2)*) -- Array of (azimuth_degrees, elevation_degrees) for each patch center. * **directions** (*np.ndarray, shape (145, 3)*) -- Unit direction vectors (dx, dy, dz) pointing to each patch center. * **solid_angles** (*np.ndarray, shape (145,)*) -- Solid angle (steradians) of each patch. .. rubric:: References Tregenza, P.R. (1987). "Subdivision of the sky hemisphere for luminance measurements." Lighting Research & Technology, 19(1), 13-14. .. py:function:: get_tregenza_patch_index(azimuth_deg, elevation_deg) Get the Tregenza patch index for a given sun position. :param azimuth_deg: Solar azimuth in degrees (0-360, measured clockwise from north). :type azimuth_deg: float :param elevation_deg: Solar elevation in degrees (0-90). :type elevation_deg: float :returns: Patch index (0-144), or -1 if below horizon. :rtype: int .. py:function:: get_tregenza_patch_index_fast(azimuth_deg, elevation_deg) Numba-accelerated version of get_tregenza_patch_index. :param azimuth_deg: Solar azimuth in degrees (0-360). :type azimuth_deg: float :param elevation_deg: Solar elevation in degrees (0-90). :type elevation_deg: float :returns: Patch index (0-144), or -1 if below horizon. :rtype: int .. py:function:: generate_reinhart_patches(mf=4) Generate Reinhart sky patches (subdivided Tregenza). The Reinhart subdivision increases resolution by subdividing each Tregenza patch by a multiplication factor (MF). With MF=4, this yields 2305 patches. :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 :type mf: int :returns: * **patches** (*np.ndarray, shape (N, 2)*) -- Array of (azimuth_degrees, elevation_degrees) for each patch center. * **directions** (*np.ndarray, shape (N, 3)*) -- Unit direction vectors (dx, dy, dz) for each patch center. * **solid_angles** (*np.ndarray, shape (N,)*) -- Solid angle (steradians) of each patch. .. rubric:: References 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. .. py:function:: generate_uniform_grid_patches(n_azimuth=36, n_elevation=9) Generate uniform grid sky patches. Simple subdivision with equal azimuth and elevation spacing. Note: This creates non-equal solid angle patches (smaller near zenith). :param n_azimuth: Number of azimuth divisions (default: 36 = 10° spacing). :type n_azimuth: int :param n_elevation: Number of elevation divisions (default: 9 = 10° spacing). :type n_elevation: int :returns: * **patches** (*np.ndarray, shape (N, 2)*) -- Array of (azimuth_degrees, elevation_degrees) for each patch center. * **directions** (*np.ndarray, shape (N, 3)*) -- Unit direction vectors for each patch center. * **solid_angles** (*np.ndarray, shape (N,)*) -- Solid angle (steradians) of each patch. .. py:function:: generate_fibonacci_patches(n_patches=145) 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). :type n_patches: int :returns: * **patches** (*np.ndarray, shape (N, 2)*) -- Array of (azimuth_degrees, elevation_degrees) for each patch center. * **directions** (*np.ndarray, shape (N, 3)*) -- Unit direction vectors for each patch center. * **solid_angles** (*np.ndarray, shape (N,)*) -- Approximate solid angle per patch (uniform for Fibonacci). .. py:function:: bin_sun_positions_to_patches(azimuth_arr, elevation_arr, dni_arr, method='tregenza', **kwargs) Bin hourly sun positions into sky patches and aggregate DNI. This is the key optimization for cumulative solar irradiance: instead of tracing rays for every hourly sun position, aggregate DNI values for each sky patch and trace rays once per patch. :param azimuth_arr: Array of solar azimuth values in degrees. :type azimuth_arr: np.ndarray :param elevation_arr: Array of solar elevation values in degrees. :type elevation_arr: np.ndarray :param dni_arr: Array of Direct Normal Irradiance values (W/m²). :type dni_arr: np.ndarray :param method: Sky discretization method: "tregenza", "reinhart", "uniform", "fibonacci". :type method: str :param \*\*kwargs: Additional parameters for patch generation (e.g., mf for Reinhart). :type \*\*kwargs: dict :returns: * **patch_directions** (*np.ndarray, shape (N, 3)*) -- Unit direction vectors for each patch. * **patch_cumulative_dni** (*np.ndarray, shape (N,)*) -- Cumulative DNI (W·h/m²) for each patch. * **patch_solid_angles** (*np.ndarray, shape (N,)*) -- Solid angle of each patch. * **patch_hours** (*np.ndarray, shape (N,)*) -- Number of hours with sun in each patch. .. py:function:: bin_sun_positions_to_tregenza_fast(azimuth_arr, elevation_arr, dni_arr) Numba-accelerated binning of sun positions to Tregenza patches. :param azimuth_arr: Array of solar azimuth values in degrees. :type azimuth_arr: np.ndarray :param elevation_arr: Array of solar elevation values in degrees. :type elevation_arr: np.ndarray :param dni_arr: Array of Direct Normal Irradiance values (W/m²). :type dni_arr: np.ndarray :returns: * **cumulative_dni** (*np.ndarray, shape (145,)*) -- Cumulative DNI (W·h/m²) for each Tregenza patch. * **hours_count** (*np.ndarray, shape (145,)*) -- Number of hours with sun in each patch. .. py:function:: get_patch_info(method='tregenza', **kwargs) Get information about a sky discretization method. :param method: Sky discretization method. :type method: str :param \*\*kwargs: Additional parameters for the method. :type \*\*kwargs: dict :returns: Dictionary with patch count, method name, and parameters. :rtype: dict .. py:function:: visualize_sky_patches(method='tregenza', ax=None, show=True, **kwargs) Visualize sky patches on a polar plot. :param method: Sky discretization method. :type method: str :param ax: Existing polar axis to plot on. :type ax: matplotlib axis, optional :param show: Whether to call plt.show(). :type show: bool :param \*\*kwargs: Additional parameters for patch generation. :type \*\*kwargs: dict :returns: **ax** -- The plot axis. :rtype: matplotlib axis