Source code for colour.phenomena.sky.wilkie2021

"""
Prague Sky Model - Wilkie et al. (2021)
=======================================

Implement the *Prague Sky Model* for physically-based sky radiance, sun
radiance, transmittance and polarisation as presented in
*Wilkie et al. (2021)* and *Vevoda et al. (2022)*.

-   :func:`colour.phenomena.sky.load_dataset_Wilkie2021`
-   :func:`colour.phenomena.sky.compute_sky_parameters_Wilkie2021`
-   :func:`colour.phenomena.sky.sky_radiance_Wilkie2021`
-   :func:`colour.phenomena.sky.sun_radiance_Wilkie2021`
-   :func:`colour.phenomena.sky.sky_polarisation_Wilkie2021`
-   :func:`colour.phenomena.sky.sky_transmittance_Wilkie2021`

References
----------
-   :cite:`Wilkie2021` : Wilkie, A., Vevoda, P., Bashford-Rogers, T., Hošek,
    L., Iser, T., Kolářová, M., Rittig, T., & Křivánek, J. (2021). A fitted
    radiance and attenuation model for realistic atmospheres. ACM Transactions
    on Graphics, 40(4), 1-14. doi:10.1145/3450626.3459758
-   :cite:`Vevoda2022` : Vévoda, P., Bashford-Rogers, T., Kolářová, M., &
    Wilkie, A. (2022). A Wide Spectral Range Sky Radiance Model. Computer
    Graphics Forum, 41(7), 291-298. doi:10.1111/cgf.14677
"""

from __future__ import annotations

import os
import struct
import typing
from dataclasses import dataclass, field

import numpy as np

if typing.TYPE_CHECKING:
    from colour.hints import ArrayLike, NDArrayFloat, NDArrayReal

from colour.algebra import lerp, linear_interpolation_index_and_factor
from colour.geometry.intersection import intersect_ray_circle_2d
from colour.utilities import (
    MixinDataclassIterable,
    Structure,
    as_float_array,
    as_int_array,
    download_url,
    zeros,
)

__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__all__ = [
    "ROOT_COLOUR_SCIENCE",
    "RECORD_ID_ZENODO",
    "ROOT_DATASET",
    "PATH_PRAGUE_SKY_MODEL_DATASET_GROUND",
    "PATH_PRAGUE_SKY_MODEL_DATASET_FULL",
    "PATH_PRAGUE_SKY_MODEL_DATASET_SWIR",
    "CONSTANTS_WILKIE2021",
    "SUN_RAD_TABLE",
    "Metadata_SkyDataset",
    "SkyDataset_Wilkie2021",
    "SkyParameters_Wilkie2021",
    "compute_sky_parameters_Wilkie2021",
    "sky_radiance_Wilkie2021",
    "sun_radiance_Wilkie2021",
    "sky_polarisation_Wilkie2021",
    "sky_transmittance_Wilkie2021",
]

ROOT_COLOUR_SCIENCE: str = os.path.join(os.path.expanduser("~"), ".colour-science")

RECORD_ID_ZENODO: str = "19140728"
"""*Zenodo* record ID for the *Prague Sky Model* datasets."""

URL_ZENODO: str = f"https://zenodo.org/records/{RECORD_ID_ZENODO}/files"
"""*Zenodo* base URL for the *Prague Sky Model* dataset files."""

ROOT_DATASET: str = os.path.join(
    ROOT_COLOUR_SCIENCE, "colour-datasets", RECORD_ID_ZENODO, "dataset"
)

PATH_PRAGUE_SKY_MODEL_DATASET_GROUND: str = os.path.join(
    ROOT_DATASET, "PragueSkyModelDatasetGround.dat"
)
"""Path to the *Prague Sky Model* ground-level dataset."""

PATH_PRAGUE_SKY_MODEL_DATASET_FULL: str = os.path.join(
    ROOT_DATASET, "PragueSkyModelDatasetFull.dat"
)
"""Path to the *Prague Sky Model* full dataset."""

PATH_PRAGUE_SKY_MODEL_DATASET_SWIR: str = os.path.join(
    ROOT_DATASET, "PragueSkyModelDatasetSWIR.dat"
)
"""Path to the *Prague Sky Model* SWIR dataset."""

# fmt: off
SUN_RAD_TABLE: NDArrayFloat = as_float_array((
    4022.51, 4592.96, 5058.84, 5552.97, 6024.88, 6443.47, 6836.2, 7175.78,
    7380.47, 7437.44, 7391.9, 7266.06, 7121.19, 7034.24, 7024.24, 7082.31,
    7209.73, 7384.6, 7563.43, 7739.25, 7905.7, 8025.18, 8096.11, 8202.27,
    8337.15, 8602.3, 8965.52, 9316.67, 9565.64, 9814.45, 9829.41, 10184.0,
    10262.6, 10375.7, 10276.0, 10179.3, 10156.6, 10750.7, 11134.0, 11463.6,
    11860.4, 12246.2, 12524.4, 12780.0, 13187.4, 13632.4, 13985.9, 13658.3,
    13377.4, 13358.3, 13239.0, 13119.8, 13096.2, 13184.0, 13243.5, 13018.4,
    12990.4, 13159.1, 13230.8, 13258.6, 13209.9, 13343.2, 13404.8, 13305.4,
    13496.3, 13979.1, 14153.8, 14188.4, 14122.7, 13825.4, 14033.3, 13914.1,
    13837.4, 14117.2, 13982.3, 13864.5, 14118.4, 14545.7, 15029.3, 15615.3,
    15923.5, 16134.8, 16574.5, 16509.0, 16336.5, 16146.6, 15965.1, 15798.6,
    15899.8, 16125.4, 15854.3, 15986.7, 15739.7, 15319.1, 15121.5, 15220.2,
    15041.2, 14917.7, 14487.8, 14011.0, 14165.7, 14189.5, 14540.7, 14797.5,
    14641.5, 14761.6, 15153.7, 14791.8, 14907.6, 15667.4, 16313.5, 16917.0,
    17570.5, 18758.1, 20250.6, 21048.1, 21626.1, 22811.6, 23577.2, 23982.6,
    24062.1, 23917.9, 23914.1, 23923.2, 24052.6, 24228.6, 24360.8, 24629.6,
    24774.8, 24648.3, 24666.5, 24938.6, 24926.3, 24693.1, 24613.5, 24631.7,
    24569.8, 24391.5, 24245.7, 24084.4, 23713.7, 22985.4, 22766.6, 22818.9,
    22834.3, 22737.9, 22791.6, 23086.3, 23377.7, 23461.0, 23935.5, 24661.7,
    25086.9, 25520.1, 25824.3, 26198.0, 26350.2, 26375.4, 26731.2, 27250.4,
    27616.0, 28145.3, 28405.9, 28406.8, 28466.2, 28521.5, 28783.8, 29025.1,
    29082.6, 29081.3, 29043.1, 28918.9, 28871.6, 29049.0, 29152.5, 29163.2,
    29143.4, 28962.7, 28847.9, 28854.0, 28808.7, 28624.1, 28544.2, 28461.4,
    28411.1, 28478.0, 28469.8, 28513.3, 28586.5, 28628.6, 28751.5, 28948.9,
    29051.0, 29049.6, 29061.7, 28945.7, 28672.8, 28241.5, 27903.2, 27737.0,
    27590.9, 27505.6, 27270.2, 27076.2, 26929.1, 27018.2, 27206.8, 27677.2,
    27939.9, 27923.9, 27899.2, 27725.4, 27608.4, 27599.4, 27614.6, 27432.4,
    27460.4, 27392.4, 27272.0, 27299.1, 27266.8, 27386.5, 27595.9, 27586.9,
    27504.8, 27480.6, 27329.8, 26968.4, 26676.3, 26344.7, 26182.5, 26026.3,
    25900.3, 25842.9, 25885.4, 25986.5, 26034.5, 26063.5, 26216.9, 26511.4,
    26672.7, 26828.5, 26901.8, 26861.5, 26865.4, 26774.2, 26855.8, 27087.1,
    27181.3, 27183.1, 27059.8, 26834.9, 26724.3, 26759.6, 26725.9, 26724.6,
    26634.5, 26618.5, 26560.1, 26518.7, 26595.3, 26703.2, 26712.7, 26733.9,
    26744.3, 26764.4, 26753.2, 26692.7, 26682.7, 26588.1, 26478.0, 26433.7,
    26380.7, 26372.9, 26343.3, 26274.7, 26162.3, 26160.5, 26210.0, 26251.2,
    26297.9, 26228.9, 26222.3, 26269.7, 26295.6, 26317.9, 26357.5, 26376.1,
    26342.4, 26303.5, 26276.7, 26349.2, 26390.0, 26371.6, 26346.7, 26327.6,
    26274.2, 26247.3, 26228.7, 26152.1, 25910.3, 25833.2, 25746.5, 25654.3,
    25562.0, 25458.8, 25438.0, 25399.1, 25324.3, 25350.0, 25514.0, 25464.9,
    25398.5, 25295.2, 25270.2, 25268.4, 25240.6, 25184.9, 25149.6, 25123.9,
    25080.3, 25027.9, 25012.3, 24977.9, 24852.6, 24756.4, 24663.5, 24483.6,
    24398.6, 24362.6, 24325.1, 24341.7, 24288.7, 24284.2, 24257.3, 24178.8,
    24097.6, 24175.6, 24175.7, 24139.7, 24088.1, 23983.2, 23902.7, 23822.4,
    23796.2, 23796.9, 23814.5, 23765.5, 23703.0, 23642.0, 23592.6, 23552.0,
    23514.6, 23473.5, 23431.0, 23389.3, 23340.0, 23275.1, 23187.3, 23069.5,
    22967.0, 22925.3, 22908.9, 22882.5, 22825.0, 22715.4, 22535.5, 22267.1,
    22029.4, 21941.6, 21919.5, 21878.8, 21825.6, 21766.0, 21728.9, 21743.2,
    21827.1, 21998.7, 22159.4, 22210.0, 22187.2, 22127.2, 22056.2, 22000.2,
    21945.9, 21880.2, 21817.1, 21770.3, 21724.3, 21663.2, 21603.3, 21560.4,
    21519.8, 21466.2, 21401.6, 21327.7, 21254.2, 21190.7, 21133.6, 21079.3,
    21024.0, 20963.7, 20905.5, 20856.6, 20816.6, 20785.2, 20746.7, 20685.3,
    20617.8, 20561.1, 20500.4, 20421.2, 20333.4, 20247.0, 20175.3, 20131.4,
    20103.2, 20078.5, 20046.8, 19997.2, 19952.9, 19937.2, 19930.8, 19914.4,
    19880.8, 19823.0, 19753.8, 19685.9, 19615.3, 19537.5, 19456.8, 19377.6,
    19309.4, 19261.9, 19228.0, 19200.5, 19179.5, 19164.8, 19153.1, 19140.6,
    19129.2, 19120.6, 19104.5, 19070.6, 19023.9, 18969.3, 18911.4, 18855.0,
    18798.6, 18740.8, 18672.7, 18585.2, 18501.0, 18442.4, 18397.5, 18353.9,
    18313.2, 18276.8, 18248.3, 18231.2, 18224.0, 18225.4, 18220.1, 18192.6,
    18155.1, 18119.8, 18081.6, 18035.6, 17987.4, 17942.8, 17901.7, 17864.2,
    17831.1, 17802.9, 17771.5, 17728.6, 17669.7, 17590.1, 17509.5, 17447.4,
    17396.0, 17347.4, 17300.3, 17253.2, 17206.1, 17159.0, 17127.6, 17127.6,
    17133.6, 17120.4, 17097.2, 17073.3, 17043.7, 17003.4, 16966.3, 16946.3,
    16930.9, 16907.7, 16882.7, 16862.0, 16837.8, 16802.1, 16759.2, 16713.6,
    16661.8, 16600.8, 16542.6, 16499.4, 16458.7, 16408.0, 16360.6, 16329.5,
    16307.4, 16286.7, 16264.9, 16239.6, 16207.8, 16166.8, 16118.2, 16064.0,
    16011.2, 15966.9, 15931.9, 15906.9, 15889.1, 15875.5, 15861.2, 15841.3,
    15813.1, 15774.2, 15728.8, 15681.4, 15630.0, 15572.9, 15516.5, 15467.2,
    15423.0, 15381.6, 15354.4, 15353.0, 15357.3, 15347.3, 15320.2, 15273.1,
    15222.0, 15183.1, 15149.6, 15114.6, 15076.8, 15034.6, 14992.9, 14996.7,
    14959.9, 14915.2, 14861.9, 14810.6, 14762.1, 14708.4, 14657.6, 14594.7,
    14508.8, 14423.0, 14344.9, 14271.9, 14219.1, 14189.1, 14159.1, 14128.6,
    14100.4, 14071.0, 14037.9, 13995.2, 13943.4, 13891.4, 13842.4, 13804.1,
    13782.4, 13791.1, 13821.6, 13853.0, 13877.6, 13898.1, 13900.4, 13880.8,
    13859.8, 13837.4, 13810.9, 13783.0, 13757.6, 13751.4, 13764.6, 13780.8,
    13797.3, 13811.4, 13806.5, 13783.0, 13757.3, 13727.3, 13695.7, 13662.4,
    13626.9, 13593.5, 13564.5, 13535.3, 13506.1, 13477.7, 13445.7, 13407.6,
    13367.9, 13327.3, 13284.9, 13240.2, 13195.4, 13146.7, 13093.4, 13038.5,
    12982.6, 12926.5, 12873.9, 12823.0, 12770.6, 12720.0, 12670.5, 12623.1,
    12579.7, 12536.1, 12489.0, 12438.8, 12384.7, 12327.6, 12271.9, 12222.9,
    12180.0, 12142.7, 12110.1, 12079.9, 12047.1, 12014.1, 11981.5, 11948.4,
    11916.6, 11883.9, 11847.8, 11813.5, 11782.2, 11754.1, 11731.6, 11711.5,
    11688.3, 11664.0, 11638.5, 11609.9, 11581.5, 11551.8, 11519.0, 11482.2,
    11443.1, 11403.0, 11363.2, 11324.4, 11287.6, 11254.0, 11222.4, 11193.7,
    11167.0, 11144.3, 11124.3, 11104.7, 11084.5, 11064.4, 11042.3, 11019.8,
    10999.1, 10983.3, 10971.7, 10963.4, 10957.1, 10954.6, 10953.0, 10947.9,
    10940.3, 10933.5, 10925.5, 10917.1, 10912.7, 10911.2, 10909.5, 10908.5,
    10907.8, 10909.0, 10913.3, 10915.2, 10913.6, 10912.4, 10909.5, 10904.4,
    10902.5, 10904.6, 10906.8, 10906.5, 10904.3, 10898.1, 10887.1, 10872.4,
    10856.5, 10839.2, 10822.5, 10804.5, 10784.0, 10763.0, 10741.9, 10716.9,
    10689.9, 10662.9, 10633.9, 10603.0, 10575.1, 10551.6, 10530.6, 10512.0,
    10495.8, 10480.2, 10463.3, 10445.1, 10425.5, 10404.6, 10383.5, 10363.6,
    10344.7, 10326.9, 10310.2, 10293.6, 10276.4, 10258.5, 10239.8, 10220.5,
    10200.9, 10181.4, 10162.1, 10142.8, 10123.7, 10104.2, 10083.6, 10062.0,
    10039.4, 10015.8, 9991.44, 9966.56, 9941.17, 9915.27, 9888.87, 9862.37,
    9836.19, 9810.32, 9784.77, 9759.55, 9735.05, 9711.69, 9689.47, 9668.4,
    9648.47, 9628.83, 9608.61, 9587.82, 9566.46, 9544.54, 9522.7, 9501.63,
    9481.32, 9461.77, 9442.98, 9424.13, 9404.39, 9383.76, 9362.24, 9339.84,
    9317.15, 9294.77, 9272.72, 9250.98, 9229.56, 9208.1, 9186.27, 9164.05,
    9141.46, 9118.48, 9095.66, 9073.54, 9052.12, 9031.4, 9011.37, 8992.33,
    8974.56, 8958.06, 8942.82, 8928.86, 8915.56, 8902.33, 8889.16, 8876.05,
    8863.01, 8850.03, 8837.11, 8824.26, 8811.47, 8798.74, 8785.76, 8772.21,
    8758.09, 8743.4, 8728.13, 8712.52, 8696.78, 8680.91, 8664.91, 8648.79,
    8632.61, 8616.42, 8600.24, 8584.05, 8567.87, 8551.78, 8535.88, 8520.17,
    8504.65, 8489.32, 8474.06, 8458.73, 8443.34, 8427.88, 8412.36, 8397.03,
    8382.15, 8367.71, 8353.71, 8340.16, 8326.52, 8312.23, 8297.32, 8281.77,
    8265.58, 8248.95, 8232.07, 8214.93, 8197.54, 8179.9, 8162.35, 8145.24,
    8128.58, 8112.36, 8096.59, 8081.04, 8065.49, 8049.94, 8034.39, 8018.84,
    8003.29, 7987.74, 7972.19, 7956.64, 7941.08, 7925.95, 7911.63, 7898.15,
    7885.48, 7873.65, 7862.06, 7850.16, 7837.94, 7825.41, 7812.56, 7799.29,
    7785.52, 7771.24, 7756.45, 7741.15, 7725.73, 7710.56, 7695.64, 7680.98,
    7666.57, 7652.29, 7638.01, 7623.73, 7609.45, 7595.17, 7580.95, 7566.86,
    7552.89, 7539.06, 7525.35, 7511.22, 7496.15, 7480.12, 7463.14, 7445.21,
    7427.19, 7409.92, 7393.42, 7377.68, 7362.7, 7348.26, 7334.14, 7320.33,
    7306.85, 7293.68, 7280.92, 7268.67, 7256.93, 7245.69, 7234.96, 7224.14,
    7212.62, 7200.4, 7187.49, 7173.87, 7160.26, 7147.34, 7135.12, 7123.6,
    7112.78, 7102.47, 7092.47, 7082.79, 7073.43, 7064.38, 7055.31, 7045.85,
    7036.01, 7025.79, 7015.19, 7004.69, 6994.76, 6985.39, 6976.6, 6968.38,
    6960.29, 6951.88, 6943.15, 6934.11, 6924.75, 6915.54, 6906.97, 6899.04,
    6891.74, 6885.08, 6878.54, 6871.62, 6864.32, 6856.64, 6848.58, 6839.38,
    6828.27, 6815.26, 6800.34, 6783.52, 6766.16, 6749.63, 6733.92, 6719.04,
    6704.98, 6691.36, 6677.81, 6664.32, 6650.9, 6637.54, 6624.56, 6612.28,
    6600.69, 6589.81, 6579.62, 6569.43, 6558.55, 6546.96, 6534.68, 6521.7,
    6508.44, 6495.3, 6482.29, 6469.4, 6456.64, 6444.46, 6433.29, 6423.13,
    6413.99, 6405.87, 6397.87, 6389.11, 6379.59, 6369.31, 6358.26, 6347.19,
    6336.81, 6327.13, 6318.15, 6309.87, 6302.25, 6295.27, 6288.92, 6283.21,
    6278.13, 6273.24, 6268.1, 6262.71, 6257.06, 6251.15, 6245.0, 6238.59,
    6231.92, 6225.0, 6217.83, 6210.09, 6201.46, 6191.94, 6181.53, 6170.23,
    6158.9, 6148.39, 6138.71, 6129.86, 6121.83, 6113.96, 6105.58, 6096.7,
    6087.3, 6077.4, 6066.93, 6055.82, 6044.08, 6031.7, 6018.69, 6005.42,
    5992.29, 5979.27, 5966.39, 5953.63, 5940.81, 5927.74, 5914.41, 5900.82,
    5886.99, 5873.09, 5859.31, 5845.67, 5832.15, 5818.75, 5805.58, 5792.73,
    5780.2, 5767.98, 5756.08, 5744.3, 5732.47, 5720.56, 5708.6, 5696.57,
    5684.26, 5671.44, 5658.11, 5644.27, 5629.93, 5615.46, 5601.24, 5587.27,
    5573.56, 5560.11, 5546.94, 5534.08, 5521.55, 5509.33, 5497.43, 5485.72,
    5474.07, 5462.49, 5450.97, 5439.51, 5428.15, 5416.92, 5405.81, 5394.83,
    5383.97, 5373.12, 5362.14, 5351.03, 5339.8, 5328.44, 5317.2, 5306.35,
    5295.88, 5285.78, 5276.07, 5266.58, 5257.16, 5247.8, 5238.5, 5229.26,
    5220.09, 5210.98, 5201.94, 5192.96, 5184.04, 5175.28, 5166.78, 5158.52,
    5150.53, 5142.78, 5135.13, 5127.42, 5119.65, 5111.81, 5103.91, 5095.97,
    5088.04, 5080.1, 5072.17, 5064.24, 5056.59, 5049.51, 5043.01, 5037.07,
    5031.71, 5026.28, 5020.16, 5013.33, 5005.81, 4997.59, 4989.02, 4980.45,
    4971.89, 4963.32, 4954.75, 4946.08, 4937.23, 4928.19, 4918.95, 4909.52,
    4899.85, 4889.85, 4879.53, 4868.9, 4857.95, 4847.1, 4836.75, 4826.92,
    4817.59, 4808.76, 4799.81, 4790.1, 4779.63, 4768.4, 4756.4, 4744.4,
    4733.17, 4722.7, 4712.99, 4704.04, 4695.31, 4686.26, 4676.9, 4667.22,
    4657.23, 4646.91, 4636.28, 4625.33, 4614.06, 4602.48, 4590.96, 4579.89,
    4569.25, 4559.07, 4549.32, 4539.87, 4530.54, 4521.33, 4512.26, 4503.31,
    4494.39, 4485.41, 4476.36, 4467.26, 4458.08, 4449.32, 4441.45, 4434.47,
    4428.38, 4423.17, 4418.16, 4412.64, 4406.61, 4400.07, 4393.03, 4386.11,
    4379.95, 4374.55, 4369.92, 4366.05, 4362.21, 4357.67, 4352.43, 4346.5,
    4339.87, 4332.92, 4326.03, 4319.21, 4312.45, 4305.75, 4299.09, 4292.42,
    4285.76, 4279.09, 4272.43, 4265.76, 4259.1, 4252.44, 4245.77, 4239.11,
    4232.7, 4226.79, 4221.4, 4216.51, 4212.13, 4207.91, 4203.5, 4198.9,
    4194.11, 4189.12, 4183.63, 4177.32, 4170.18, 4162.21, 4153.42, 4144.34,
    4135.52, 4126.95, 4118.64, 4110.58, 4102.74, 4095.09, 4087.63, 4080.36,
    4073.29, 4066.31, 4059.32, 4052.34, 4045.36, 4038.38, 4031.52, 4024.92,
    4018.57, 4012.48, 4006.64, 4000.83, 3994.84, 3988.65, 3982.27, 3975.7,
    3968.97, 3962.12, 3955.13, 3948.03, 3940.79, 3933.27, 3925.3, 3916.89,
    3908.04, 3898.74, 3889.44, 3880.59, 3872.18, 3864.21, 3856.69, 3849.52,
    3842.6, 3835.94, 3829.52, 3823.37, 3817.21, 3810.8, 3804.14, 3797.22,
    3790.05, 3782.59, 3774.81, 3766.72, 3758.31, 3749.58, 3740.73, 3731.94,
    3723.21, 3714.55, 3705.95, 3697.44, 3689.06, 3680.81, 3672.69, 3664.69,
    3656.69, 3648.57, 3640.32, 3631.94, 3623.43, 3615.12, 3607.31, 3600.01,
    3593.22, 3586.94, 3580.97, 3575.13, 3569.42, 3563.83, 3558.37, 3552.92,
    3547.33, 3541.62, 3535.78, 3529.81, 3524.04, 3518.77, 3514.01, 3509.75,
    3506.01, 3502.52, 3499.03, 3495.54, 3492.05, 3488.56, 3485.13, 3481.83,
    3478.65, 3475.61, 3472.69, 3469.74, 3466.59, 3463.26, 3459.74, 3456.03,
    3452.22, 3448.41, 3444.6, 3440.79, 3436.98, 3433.3, 3429.88, 3426.7,
    3423.78, 3421.12, 3418.51, 3415.79, 3412.93, 3409.95, 3406.84, 3403.66,
    3400.49, 3397.32, 3394.14, 3390.97, 3387.76, 3384.49, 3381.16, 3377.77,
    3374.31, 3370.66, 3366.69, 3362.41, 3357.8, 3352.88, 3347.65, 3342.09,
    3336.22, 3330.04, 3323.53, 3317.02, 3310.84, 3304.96, 3299.41, 3294.17,
    3289.13, 3284.15, 3279.23, 3274.37, 3269.58, 3264.79, 3259.93, 3255.01,
    3250.03, 3244.98, 3239.78, 3234.32, 3228.61, 3222.64, 3216.42, 3210.17,
    3204.11, 3198.24, 3192.56, 3187.07, 3181.54, 3175.77, 3169.74, 3163.45,
    3156.92, 3150.09, 3142.95, 3135.49, 3127.72, 3119.63, 3111.69, 3104.39,
    3097.73, 3091.7, 3086.3, 3081.26, 3076.28, 3071.36, 3066.5, 3061.71,
    3056.63, 3050.92, 3044.57, 3037.59, 3029.97, 3021.91, 3013.6, 3005.03,
    2996.21, 2987.13, 2977.89, 2968.6, 2959.23, 2949.81, 2940.32, 2931.02,
    2922.17, 2913.76, 2905.79, 2898.27, 2890.69, 2882.53, 2873.8, 2864.5,
    2854.63, 2844.7, 2835.21, 2826.17, 2817.57, 2809.41, 2801.82, 2794.94,
    2788.75, 2783.26, 2778.47, 2773.58, 2767.8, 2761.14, 2753.59, 2745.14,
    2736.42, 2728.01, 2719.91, 2712.14, 2704.68, 2697.51, 2690.59, 2683.93,
    2677.52, 2671.36, 2665.39, 2659.55, 2653.84, 2648.26, 2642.8, 2637.37,
    2631.88, 2626.33, 2620.71, 2615.03, 2609.22, 2603.22, 2597.03, 2590.66,
    2584.09, 2577.58, 2571.39, 2565.52, 2559.97, 2554.73, 2549.72, 2544.83,
    2540.07, 2535.43, 2530.93, 2526.29, 2521.28, 2515.89, 2510.11, 2503.95,
    2497.73, 2491.77, 2486.05, 2480.6, 2475.39, 2470.12, 2464.47, 2458.44,
    2452.03, 2445.24, 2438.42, 2431.91, 2425.72, 2419.85, 2414.3, 2409.0,
    2403.89, 2398.97, 2394.24, 2389.7, 2385.23, 2380.69, 2376.09, 2371.42,
    2366.7, 2361.97, 2357.3, 2352.7, 2348.16, 2343.69, 2339.09, 2334.17,
    2328.93, 2323.38, 2317.51, 2311.44, 2305.32, 2299.13, 2292.88, 2286.56,
    2280.28, 2274.12, 2268.09, 2262.19, 2256.41, 2250.64, 2244.74, 2238.71,
    2232.55, 2226.26, 2220.08, 2214.21, 2208.65, 2203.42, 2198.5, 2193.67,
    2188.72, 2183.64, 2178.44, 2173.11, 2167.74, 2162.44, 2157.21, 2152.03,
    2146.93, 2141.69, 2136.14, 2130.26, 2124.08, 2117.57, 2110.91, 2104.24,
    2097.58, 2090.91, 2084.25, 2077.93, 2072.31, 2067.4, 2063.17, 2059.65,
    2056.32, 2052.67, 2048.7, 2044.42, 2039.82, 2035.09, 2030.42, 2025.82,
    2021.28, 2016.81, 2012.49, 2008.43, 2004.62, 2001.07, 1997.77, 1994.53,
    1991.17, 1987.68, 1984.06, 1980.31, 1976.66, 1973.33, 1970.32, 1967.62,
    1965.24, 1963.08, 1961.05, 1959.14, 1957.37, 1955.72, 1954.32, 1953.31,
    1952.67, 1952.42, 1952.54, 1952.8, 1952.92, 1952.92, 1952.8, 1952.54,
    1952.0, 1951.02, 1949.59, 1947.72, 1945.4, 1942.93, 1940.58, 1938.36,
    1936.26, 1934.3, 1932.39, 1930.49, 1928.58, 1926.68, 1924.77, 1922.78,
    1920.59, 1918.21, 1915.64, 1912.87, 1910.11, 1907.54, 1905.16, 1902.97,
    1900.97, 1898.94, 1896.66, 1894.12, 1891.33, 1888.28, 1884.91, 1881.17,
    1877.04, 1872.54, 1867.65, 1862.76, 1858.26, 1854.13, 1850.39, 1847.02,
    1843.88, 1840.8, 1837.79, 1834.84, 1831.95, 1829.16, 1826.49, 1823.95,
    1821.54, 1819.25, 1817.16, 1815.32, 1813.73, 1812.4, 1811.32, 1810.27,
    1809.03, 1807.61, 1805.99, 1804.18, 1802.27, 1800.37, 1798.47, 1796.56,
    1794.66, 1792.75, 1790.85, 1788.95, 1787.04, 1785.14, 1783.33, 1781.71,
    1780.28, 1779.04, 1778.0, 1776.95, 1775.71, 1774.28, 1772.67, 1770.86,
    1768.76, 1766.29, 1763.43, 1760.19, 1756.58, 1752.77, 1748.96, 1745.15,
    1741.34, 1737.53, 1733.63, 1729.54, 1725.25, 1720.78, 1716.11, 1711.13,
    1705.7, 1699.83, 1693.52, 1686.76, 1680.14, 1674.25, 1669.1, 1664.67,
    1660.97, 1657.64, 1654.31, 1650.97, 1647.64, 1644.31, 1640.95, 1637.52,
    1634.03, 1630.47, 1626.86, 1623.21, 1619.56, 1615.91, 1612.26, 1608.61,
    1605.01, 1601.5, 1598.09, 1594.77, 1591.55, 1588.38, 1585.2, 1582.03,
    1578.85, 1575.68, 1572.43, 1569.02, 1565.45, 1561.72, 1557.83, 1553.86,
    1549.9, 1545.93, 1541.96, 1538.0, 1534.06, 1530.19, 1526.38, 1522.64,
    1518.95, 1515.3, 1511.65, 1508.0, 1504.36, 1500.71, 1496.99, 1493.15,
    1489.19, 1485.09, 1480.87, 1476.59, 1472.3, 1468.02, 1463.73, 1459.45,
    1455.3, 1451.41, 1447.79, 1444.43, 1441.33, 1438.37, 1435.41, 1432.45,
    1429.49, 1426.52, 1423.56, 1420.6, 1417.64, 1414.68, 1411.71, 1408.74,
    1405.75, 1402.73, 1399.7, 1396.64, 1393.57, 1390.5, 1387.44, 1384.37,
    1381.3, 1378.23, 1375.16, 1372.1, 1369.03, 1365.96, 1362.91, 1359.91,
    1356.95, 1354.03, 1351.15, 1348.29, 1345.44, 1342.58, 1339.73, 1336.87,
    1334.01, 1331.16, 1328.3, 1325.44, 1322.59, 1319.71, 1316.79, 1313.83,
    1310.83, 1307.78, 1304.71, 1301.64, 1298.58, 1295.51, 1292.44, 1289.37,
    1286.3, 1283.24, 1280.17, 1277.1, 1274.05, 1271.05, 1268.09, 1265.17,
    1262.29, 1259.43, 1256.58, 1253.72, 1250.87, 1248.01, 1245.15, 1242.3,
    1239.44, 1236.58, 1233.73, 1230.88, 1228.06, 1225.25, 1222.47, 1219.71,
    1216.96, 1214.21, 1211.46, 1208.71, 1205.96, 1203.21, 1200.46, 1197.71,
    1194.96, 1192.21, 1189.49, 1186.83, 1184.24, 1181.71, 1179.25, 1176.82,
    1174.38, 1171.95, 1169.52, 1167.08, 1164.65, 1162.22, 1159.78, 1157.35,
    1154.92, 1152.51, 1150.14, 1147.81, 1145.52, 1143.28, 1141.06, 1138.84,
    1136.62, 1134.4, 1132.17, 1129.95, 1127.73, 1125.51, 1123.29, 1121.07,
    1118.9, 1116.83, 1114.88, 1113.03, 1111.28, 1109.59, 1107.9, 1106.2,
    1104.51, 1102.82, 1101.13, 1099.43, 1097.74, 1096.05, 1094.36, 1092.66,
    1090.97, 1089.28, 1087.59, 1085.89, 1084.2, 1082.51, 1080.81, 1079.12,
    1077.43, 1075.74, 1074.04, 1072.35, 1070.66, 1068.97, 1067.28, 1065.62,
    1063.98, 1062.37, 1060.77, 1059.18, 1057.59, 1056.01, 1054.42, 1052.83,
    1051.25, 1049.66, 1048.07, 1046.49, 1044.9, 1043.26, 1041.5, 1039.64,
    1037.66, 1035.58, 1033.44, 1031.29, 1029.15, 1027.01, 1024.87, 1022.73,
    1020.58, 1018.44, 1016.3, 1014.16, 1012.01, 1009.87, 1007.73, 1005.59,
    1003.45, 1001.34, 999.3, 997.33, 995.43, 993.59, 991.8, 990.0,
    988.2, 986.4, 984.6, 982.8, 981.01, 979.21, 977.41, 975.61,
    973.93, 972.48, 971.26, 970.28, 969.53, 968.89, 968.26, 967.62,
    966.99, 966.35, 965.72, 965.09, 964.45, 963.82, 963.18, 962.55,
    961.91, 961.28, 960.64, 960.01, 959.28, 958.36, 957.25, 955.95,
    954.45, 952.87, 951.28, 949.69, 948.11, 946.52, 944.93, 943.35,
    941.76, 940.17, 938.59, 937.0, 935.41, 933.83, 932.24, 930.65,
    929.06, 927.45, 925.82, 924.18, 922.52, 920.85, 919.19, 917.52,
    915.85, 914.19, 912.52, 910.86, 909.19, 907.52, 905.86, 904.19,
    902.53, 900.86, 899.19, 897.53, 895.91, 894.39, 892.96, 891.62,
    890.39, 889.2, 888.01, 886.82, 885.63, 884.44, 883.25, 882.06,
    880.87, 879.68, 878.49, 877.3, 876.11, 874.92, 873.73, 872.54,
    871.31, 870.0, 868.61, 867.14, 865.59, 864.01, 862.42, 860.83,
    859.25, 857.66, 856.07, 854.49, 852.9, 851.31, 849.73, 848.14,
    846.55, 844.97, 843.38, 841.79, 840.24, 838.74, 837.32, 835.95,
    834.65, 833.38, 832.11, 830.84, 829.57, 828.3, 827.03, 825.76,
    824.5, 823.23, 821.96, 820.69, 819.42, 818.15, 816.88, 815.61,
    814.37, 813.2, 812.09, 811.04, 810.06, 809.1, 808.15, 807.2,
    806.25, 805.3, 804.34, 803.39, 802.44, 801.49, 800.53, 799.58,
    798.63, 797.68, 796.73, 795.77, 794.87, 794.06, 793.35, 792.73,
    792.2, 791.73, 791.25, 790.78, 790.3, 789.82, 789.35, 788.87,
    788.4, 787.92, 787.44, 786.97, 786.49, 786.02, 785.54, 785.06,
    784.61, 784.19, 783.81, 783.48, 783.18, 782.9, 782.62, 782.35,
    782.07, 781.79, 781.51, 781.24, 780.96, 780.68, 780.4, 780.12,
    779.85, 779.57, 779.29, 779.01, 778.68, 778.23, 777.66, 776.98,
    776.18, 775.32, 774.46, 773.61, 772.75, 771.89, 771.04, 770.18,
    769.32, 768.47, 767.61, 766.75, 765.9, 765.04, 764.18, 763.32,
    762.47, 761.61, 760.75, 759.9, 759.04, 758.25, 757.59, 757.07,
    756.68, 756.42, 756.23, 756.04, 755.85, 755.66, 755.47, 755.28,
    755.09, 754.9, 754.71, 754.52, 754.33, 754.14, 753.95, 753.76,
    753.57, 753.38, 753.19, 752.99, 752.8, 752.61, 752.43, 752.25,
    752.08, 751.91, 751.76, 751.6, 751.45, 751.3, 751.15, 751.0,
    750.84, 750.69, 750.54, 750.39, 750.2,
))
# fmt: on

CONSTANTS_WILKIE2021: Structure = Structure(
    planet_radius=6378000.0,
    safety_altitude=50.0,
    sun_radius=0.004654793,
    atmosphere_width=100000.0,
    distance_to_edge=1571524.413613,
    sun_radiance_start=280.0,
    sun_radiance_step=1.0,
    sun_radiance_end=280.0 + 1.0 * len(SUN_RAD_TABLE),
    low_altitude_threshold=0.3,
    distance_epsilon=1e-10,
)
"""
Constants for the *Prague Sky Model*.

-   ``planet_radius``: Earth radius in meters.
-   ``safety_altitude``: Safety altitude shift in meters.
-   ``sun_radius``: Angular radius of the sun in radians (0.2667 degrees).
-   ``atmosphere_width``: Width of the atmosphere in meters.
-   ``distance_to_edge``: Maximum distance to the edge of the atmosphere
    in the transmittance model.
-   ``sun_radiance_start``: Start wavelength of the solar radiance table
    in nanometers.
-   ``sun_radiance_step``: Wavelength step of the solar radiance table
    in nanometers.
-   ``sun_radiance_end``: End wavelength of the solar radiance table
    in nanometers.
-   ``low_altitude_threshold``: Altitude threshold in meters below which
    the transmittance model uses a simplified ray-atmosphere intersection
    to avoid numerical issues.
-   ``distance_epsilon``: Minimum distance used to guard against division
    by zero when computing the transmittance distance parameter.
"""


@dataclass
class Metadata_SkyDataset:
    """
    Internal metadata for radiance/polarisation coefficient layout.

    Parameters
    ----------
    rank
        Rank of the tensor decomposition.
    sun_offset
        Offset to the sun coefficients within a single rank block.
    sun_stride
        Stride between consecutive rank blocks (sun + zenith breaks).
    sun_break_points
        Break points for piecewise linear approximation of the sun
        (gamma) dimension.
    zenith_offset
        Offset to the zenith coefficients within a single rank block.
    zenith_stride
        Stride between consecutive rank blocks for the zenith dimension.
    zenith_break_points
        Break points for piecewise linear approximation of the zenith
        (alpha/zero) dimension.
    emphasis_offset
        Offset to the emphasis coefficients (radiance only).
    emphasis_break_points
        Break points for the emphasis de-weighting function (radiance
        only, empty for polarisation).
    total_coefficients_single_configuration
        Total number of coefficients for a single configuration
        (one channel, one visibility/albedo/altitude/elevation).
    total_coefficients_all_configurations
        Total number of coefficients across all configurations.
    """

    rank: int = 0
    sun_offset: int = 0
    sun_stride: int = 0
    sun_break_points: NDArrayFloat = field(default_factory=lambda: zeros(0))
    zenith_offset: int = 0
    zenith_stride: int = 0
    zenith_break_points: NDArrayFloat = field(default_factory=lambda: zeros(0))
    emphasis_offset: int = 0
    emphasis_break_points: NDArrayFloat = field(default_factory=lambda: zeros(0))
    total_coefficients_single_configuration: int = 0
    total_coefficients_all_configurations: int = 0


[docs] @dataclass class SkyDataset_Wilkie2021(MixinDataclassIterable): """ Implement support for loading and holding a *Prague Sky Model* dataset. Parameters ---------- path Path to the ``.dat`` dataset file. Attributes ---------- - :attr:`~SkyDataset_Wilkie2021.path` - :attr:`~SkyDataset_Wilkie2021.channels` - :attr:`~SkyDataset_Wilkie2021.channel_start` - :attr:`~SkyDataset_Wilkie2021.channel_width` Methods ------- - :meth:`~SkyDataset_Wilkie2021.__init__` - :meth:`~SkyDataset_Wilkie2021.read` References ---------- :cite:`Wilkie2021`, :cite:`Vevoda2022` Examples -------- >>> dataset = SkyDataset_Wilkie2021() # doctest: +SKIP >>> dataset.channels # doctest: +SKIP 11 """ path: str = PATH_PRAGUE_SKY_MODEL_DATASET_GROUND channels: int = field(default=0, init=False) channel_start: float = field(default=0.0, init=False) channel_width: float = field(default=0.0, init=False) visibilities_radiance: NDArrayFloat = field( default_factory=lambda: zeros(0), init=False ) albedos_radiance: NDArrayFloat = field(default_factory=lambda: zeros(0), init=False) altitudes_radiance: NDArrayFloat = field( default_factory=lambda: zeros(0), init=False ) elevations_radiance: NDArrayFloat = field( default_factory=lambda: zeros(0), init=False ) metadata_radiance: Metadata_SkyDataset = field( default_factory=Metadata_SkyDataset, init=False ) data_radiance: NDArrayFloat = field(default_factory=lambda: zeros(0), init=False) metadata_polarisation: Metadata_SkyDataset = field( default_factory=Metadata_SkyDataset, init=False ) data_polarisation: NDArrayFloat = field( default_factory=lambda: zeros(0), init=False ) altitude_dimension: int = field(default=0, init=False) distance_dimension: int = field(default=0, init=False) rank_transmittance: int = field(default=0, init=False) altitudes_transmittance: NDArrayFloat = field( default_factory=lambda: zeros(0), init=False ) visibilities_transmittance: NDArrayFloat = field( default_factory=lambda: zeros(0), init=False ) data_transmittance_u: NDArrayFloat = field( default_factory=lambda: zeros(0), init=False ) data_transmittance_v: NDArrayFloat = field( default_factory=lambda: zeros(0), init=False ) def __post_init__(self) -> None: """ Read the dataset from :attr:`path` if provided. If the file does not exist and the path matches one of the default dataset paths, the dataset is automatically downloaded from *Zenodo*. """ if self.path: if not os.path.isfile(self.path) and self.path.startswith(ROOT_DATASET): download_url( f"{URL_ZENODO}/{os.path.basename(self.path)}?download=1", filename=self.path, ) self.read() def read(self) -> SkyDataset_Wilkie2021: """ Read the *Prague Sky Model* dataset from the file at :attr:`path`. Returns ------- :class:`SkyDataset_Wilkie2021` *self* for chaining. """ with open(self.path, "rb") as f: # Radiance section. self.visibilities_radiance = np.frombuffer( f.read(struct.unpack("<i", f.read(4))[0] * 8), dtype="<f8" ).copy() self.albedos_radiance = np.frombuffer( f.read(struct.unpack("<i", f.read(4))[0] * 8), dtype="<f8" ).copy() self.altitudes_radiance = np.frombuffer( f.read(struct.unpack("<i", f.read(4))[0] * 8), dtype="<f8" ).copy() self.elevations_radiance = np.frombuffer( f.read(struct.unpack("<i", f.read(4))[0] * 8), dtype="<f8" ).copy() self.channels = struct.unpack("<i", f.read(4))[0] self.channel_start = struct.unpack("<d", f.read(8))[0] self.channel_width = struct.unpack("<d", f.read(8))[0] configurations = ( self.channels * len(self.elevations_radiance) * len(self.altitudes_radiance) * len(self.albedos_radiance) * len(self.visibilities_radiance) ) metadata_radiance = Metadata_SkyDataset() metadata_radiance.rank = struct.unpack("<i", f.read(4))[0] metadata_radiance.sun_break_points = np.frombuffer( f.read(struct.unpack("<i", f.read(4))[0] * 8), dtype="<f8" ).copy() metadata_radiance.zenith_break_points = np.frombuffer( f.read(struct.unpack("<i", f.read(4))[0] * 8), dtype="<f8" ).copy() metadata_radiance.emphasis_break_points = np.frombuffer( f.read(struct.unpack("<i", f.read(4))[0] * 8), dtype="<f8" ).copy() metadata_radiance.sun_offset = 0 metadata_radiance.sun_stride = len( metadata_radiance.sun_break_points ) + len(metadata_radiance.zenith_break_points) metadata_radiance.zenith_offset = len(metadata_radiance.sun_break_points) metadata_radiance.zenith_stride = metadata_radiance.sun_stride metadata_radiance.emphasis_offset = ( metadata_radiance.rank * metadata_radiance.sun_stride ) metadata_radiance.total_coefficients_single_configuration = ( metadata_radiance.emphasis_offset + len(metadata_radiance.emphasis_break_points) ) metadata_radiance.total_coefficients_all_configurations = ( metadata_radiance.total_coefficients_single_configuration * configurations ) self.metadata_radiance = metadata_radiance data_radiance = np.zeros( metadata_radiance.total_coefficients_all_configurations, dtype=np.float32, ) offset = 0 for _configuration in range(configurations): for _rank_index in range(metadata_radiance.rank): sun_coefficients = as_float_array( np.frombuffer( f.read(len(metadata_radiance.sun_break_points) * 2), dtype="<f2", ) ) zenith_scale = struct.unpack("<d", f.read(8))[0] zenith_coefficients = as_float_array( np.frombuffer( f.read(len(metadata_radiance.zenith_break_points) * 2), dtype="<f2", ) ) data_radiance[ offset : offset + len(metadata_radiance.sun_break_points) ] = as_float_array(sun_coefficients, dtype=np.float32) offset += len(metadata_radiance.sun_break_points) scaled = as_float_array( zenith_coefficients / zenith_scale, dtype=np.float32 ) data_radiance[ offset : offset + len(metadata_radiance.zenith_break_points) ] = scaled offset += len(metadata_radiance.zenith_break_points) emphasis_coefficients = as_float_array( np.frombuffer( f.read(len(metadata_radiance.emphasis_break_points) * 2), dtype="<f2", ) ) data_radiance[ offset : offset + len(metadata_radiance.emphasis_break_points) ] = as_float_array(emphasis_coefficients, dtype=np.float32) offset += len(metadata_radiance.emphasis_break_points) self.data_radiance = data_radiance # Transmittance section. self.distance_dimension = struct.unpack("<i", f.read(4))[0] self.altitude_dimension = struct.unpack("<i", f.read(4))[0] visibility_count = struct.unpack("<i", f.read(4))[0] altitude_count = struct.unpack("<i", f.read(4))[0] self.rank_transmittance = struct.unpack("<i", f.read(4))[0] self.altitudes_transmittance = as_float_array( np.frombuffer(f.read(altitude_count * 4), dtype="<f4").copy() ) self.visibilities_transmittance = as_float_array( np.frombuffer(f.read(visibility_count * 4), dtype="<f4").copy() ) total_u = ( self.distance_dimension * self.altitude_dimension * self.rank_transmittance * altitude_count ) total_v = ( visibility_count * self.rank_transmittance * self.channels * altitude_count ) self.data_transmittance_u = np.frombuffer( f.read(total_u * 4), dtype="<f4" ).copy() self.data_transmittance_v = np.frombuffer( f.read(total_v * 4), dtype="<f4" ).copy() # Polarisation section (optional). rank_bytes = f.read(4) if len(rank_bytes) >= 4: rank = struct.unpack("<i", rank_bytes)[0] metadata_polarisation = Metadata_SkyDataset() metadata_polarisation.rank = rank metadata_polarisation.sun_break_points = np.frombuffer( f.read(struct.unpack("<i", f.read(4))[0] * 8), dtype="<f8" ).copy() metadata_polarisation.zenith_break_points = np.frombuffer( f.read(struct.unpack("<i", f.read(4))[0] * 8), dtype="<f8" ).copy() metadata_polarisation.emphasis_break_points = zeros(0) metadata_polarisation.sun_offset = 0 metadata_polarisation.sun_stride = len( metadata_polarisation.sun_break_points ) + len(metadata_polarisation.zenith_break_points) metadata_polarisation.zenith_offset = len( metadata_polarisation.sun_break_points ) metadata_polarisation.zenith_stride = metadata_polarisation.sun_stride metadata_polarisation.emphasis_offset = 0 metadata_polarisation.total_coefficients_single_configuration = ( metadata_polarisation.rank * metadata_polarisation.sun_stride ) metadata_polarisation.total_coefficients_all_configurations = ( metadata_polarisation.total_coefficients_single_configuration * configurations ) coefficient_count = ( len(metadata_polarisation.sun_break_points) + len(metadata_polarisation.zenith_break_points) ) * metadata_polarisation.rank data_polarisation = np.zeros( metadata_polarisation.total_coefficients_all_configurations, dtype=np.float32, ) offset = 0 for _configuration in range(configurations): chunk = np.frombuffer( f.read(coefficient_count * 4), dtype="<f4" ).copy() data_polarisation[offset : offset + coefficient_count] = chunk offset += coefficient_count self.metadata_polarisation = metadata_polarisation self.data_polarisation = data_polarisation return self
[docs] @dataclass class SkyParameters_Wilkie2021(MixinDataclassIterable): """ Hold computed parameters for querying the *Prague Sky Model*. Parameters ---------- theta Angle between view direction and zenith in radians [0, pi]. gamma Angle between view direction and sun in radians [0, pi]. shadow Altitude-corrected shadow angle in radians [0, pi]. zero Altitude-corrected zenith angle in radians [0, pi]. elevation Sun elevation at view point in radians. altitude Observer altitude in meters. visibility Meteorological range at ground level in kilometers. albedo Ground albedo [0, 1]. References ---------- :cite:`Wilkie2021`, :cite:`Vevoda2022` """ theta: float | NDArrayFloat = 0.0 gamma: float | NDArrayFloat = 0.0 shadow: float | NDArrayFloat = 0.0 zero: float | NDArrayFloat = 0.0 elevation: float | NDArrayFloat = 0.0 altitude: float | NDArrayFloat = 0.0 visibility: float = 0.0 albedo: float = 0.0
[docs] def compute_sky_parameters_Wilkie2021( view_point: ArrayLike, view_direction: ArrayLike, sun_elevation: float, sun_azimuth: float, visibility: float, albedo: float, ) -> SkyParameters_Wilkie2021: """ Compute parameters for querying the *Prague Sky Model*. Supports batched *view_direction* inputs. If *view_direction* has shape ``(..., 3)``, the returned parameter fields will have shape ``(...)``. Parameters ---------- view_point Observer position ``[x, y, z]`` in meters with z up. view_direction View direction(s) ``[..., 3]``, unit-length normalised. sun_elevation Sun elevation at ground level at origin in radians. sun_azimuth Sun azimuth at ground level at origin in radians. visibility Meteorological range at ground level in kilometers. albedo Ground albedo [0, 1]. Returns ------- :class:`SkyParameters_Wilkie2021` Computed parameters. Fields are scalars when *view_direction* is ``(3,)`` or arrays matching the batch dimensions otherwise. References ---------- :cite:`Wilkie2021`, :cite:`Vevoda2022` """ view_point_array = as_float_array(view_point) view_direction_array = as_float_array(view_direction) view_direction_array = view_direction_array / np.linalg.norm( view_direction_array, axis=-1, keepdims=True ) center = np.array([0.0, 0.0, -CONSTANTS_WILKIE2021.planet_radius]) to_view_point = view_point_array - center to_view_point_normalised = to_view_point / np.linalg.norm(to_view_point) distance_to_view = ( np.linalg.norm(to_view_point) + CONSTANTS_WILKIE2021.safety_altitude ) to_shifted_view_point = to_view_point_normalised * distance_to_view shifted_view_point = center + to_shifted_view_point altitude = max(distance_to_view - CONSTANTS_WILKIE2021.planet_radius, 0.0) # Direction to sun. direction_to_sun = np.array( [ np.cos(sun_azimuth) * np.cos(sun_elevation), np.sin(sun_azimuth) * np.cos(sun_elevation), np.sin(sun_elevation), ] ) # Solar elevation at view point. dot_zenith_sun = float(np.dot(to_view_point_normalised, direction_to_sun)) elevation = 0.5 * np.pi - np.arccos(dot_zenith_sun) # Altitude-corrected view direction. if distance_to_view > CONSTANTS_WILKIE2021.planet_radius: look_at_point = shifted_view_point + view_direction_array correction = ( np.sqrt(distance_to_view**2 - CONSTANTS_WILKIE2021.planet_radius**2) / distance_to_view ) to_new_origin = to_view_point_normalised * (distance_to_view - correction) new_origin = center + to_new_origin correct_view = look_at_point - new_origin correct_view_n = correct_view / np.linalg.norm( correct_view, axis=-1, keepdims=True ) else: correct_view_n = view_direction_array # Gamma (sun angle) - no correction. Shape: (...,). gamma = np.arccos( np.clip(np.sum(view_direction_array * direction_to_sun, axis=-1), -1, 1) ) # Shadow angle - requires correction. shadow_angle = sun_elevation + np.pi * 0.5 shadow_direction = np.array( [ np.cos(shadow_angle) * np.cos(sun_azimuth), np.cos(shadow_angle) * np.sin(sun_azimuth), np.sin(shadow_angle), ] ) shadow = np.arccos( np.clip(np.sum(correct_view_n * shadow_direction, axis=-1), -1, 1) ) # Zenith angle (corrected and uncorrected). Shape: (...,). zero = np.arccos( np.clip(np.sum(correct_view_n * to_view_point_normalised, axis=-1), -1, 1) ) theta = np.arccos( np.clip(np.sum(view_direction_array * to_view_point_normalised, axis=-1), -1, 1) ) return SkyParameters_Wilkie2021( theta=theta, gamma=gamma, shadow=shadow, zero=zero, elevation=float(elevation), altitude=float(altitude), visibility=visibility, albedo=albedo, )
def _reconstruct_sky_model( data: NDArrayFloat, offsets: NDArrayReal, gamma_index: NDArrayReal, gamma_factor: NDArrayReal, alpha_index: NDArrayReal, alpha_factor: NDArrayReal, zero_index: NDArrayReal, zero_factor: NDArrayReal, metadata: Metadata_SkyDataset, ) -> NDArrayFloat: """ Reconstruct radiance/polarisation via inverse tensor decomposition. Parameters ---------- offsets Coefficient offsets, shape ``(*S, W)``. gamma_index, gamma_factor Sun angle interpolation parameters, shape ``(*S,)``. alpha_index, alpha_factor Zenith/shadow angle interpolation parameters, shape ``(*S,)``. zero_index, zero_factor Emphasis angle interpolation parameters, shape ``(*S,)``. Returns ------- :class:`numpy.ndarray` Reconstructed values, shape ``(*S, W)``. """ result = np.zeros(offsets.shape, dtype=np.float64) for rank_index in range(metadata.rank): sun_index = as_int_array( offsets + metadata.sun_offset + rank_index * metadata.sun_stride + gamma_index[..., None] ) sun_value = lerp( gamma_factor[..., None], as_float_array(data[sun_index]), as_float_array(data[sun_index + 1]), ) zenith_index = as_int_array( offsets + metadata.zenith_offset + rank_index * metadata.zenith_stride + alpha_index[..., None] ) zenith_value = lerp( alpha_factor[..., None], as_float_array(data[zenith_index]), as_float_array(data[zenith_index + 1]), ) result += sun_value * zenith_value if len(metadata.emphasis_break_points) > 0: emphasis_index = as_int_array( offsets + metadata.emphasis_offset + zero_index[..., None] ) emphasis_value = lerp( zero_factor[..., None], as_float_array(data[emphasis_index]), as_float_array(data[emphasis_index + 1]), ) result *= emphasis_value # The emphasis term is non-negative by construction in the # *Prague Sky Model* reference; clipping floors out fitting noise. np.maximum(result, 0.0, out=result) return result def _evaluate_sky_model( dataset: SkyDataset_Wilkie2021, parameters: SkyParameters_Wilkie2021, wavelength: NDArrayFloat, data: NDArrayFloat, metadata: Metadata_SkyDataset, ) -> NDArrayFloat: """ Evaluate the model for given wavelength. If the parameter fields have shape ``(*S,)`` and *wavelength* has shape ``(W,)``, the result has shape ``(*S, W)``. """ wavelength = np.atleast_1d(wavelength) wavelength_count = len(wavelength) # Angle parameters. gamma_index, gamma_factor = linear_interpolation_index_and_factor( parameters.gamma, metadata.sun_break_points ) elevation = as_float_array(parameters.elevation) shadow = as_float_array(parameters.shadow) zero = as_float_array(parameters.zero) if len(metadata.emphasis_break_points) > 0: alpha_value = np.where(elevation < 0, shadow, zero) alpha_index, alpha_factor = linear_interpolation_index_and_factor( alpha_value, metadata.zenith_break_points ) zero_index, zero_factor = linear_interpolation_index_and_factor( zero, metadata.emphasis_break_points ) else: alpha_index, alpha_factor = linear_interpolation_index_and_factor( zero, metadata.zenith_break_points ) zero_index = np.zeros_like(gamma_index) zero_factor = np.zeros_like(gamma_factor) # Configuration parameters. visibility_index, visibility_factor = linear_interpolation_index_and_factor( parameters.visibility, dataset.visibilities_radiance ) albedo_index, albedo_factor = linear_interpolation_index_and_factor( parameters.albedo, dataset.albedos_radiance ) altitude_index, altitude_factor = linear_interpolation_index_and_factor( parameters.altitude, dataset.altitudes_radiance ) elevation_index, elevation_factor = linear_interpolation_index_and_factor( np.degrees(elevation), dataset.elevations_radiance ) # Broadcast all parameter arrays to the common batch shape. shape = np.broadcast_shapes( np.shape(gamma_index), np.shape(alpha_index), np.shape(zero_index), np.shape(visibility_index), np.shape(albedo_index), np.shape(altitude_index), np.shape(elevation_index), ) gamma_index = np.broadcast_to(gamma_index, shape) gamma_factor = np.broadcast_to(gamma_factor, shape) alpha_index = np.broadcast_to(alpha_index, shape) alpha_factor = np.broadcast_to(alpha_factor, shape) zero_index = np.broadcast_to(zero_index, shape) zero_factor = np.broadcast_to(zero_factor, shape) visibility_index = np.broadcast_to(visibility_index, shape) visibility_factor = np.broadcast_to(visibility_factor, shape) albedo_index = np.broadcast_to(albedo_index, shape) albedo_factor = np.broadcast_to(albedo_factor, shape) altitude_index = np.broadcast_to(altitude_index, shape) altitude_factor = np.broadcast_to(altitude_factor, shape) elevation_index = np.broadcast_to(elevation_index, shape) elevation_factor = np.broadcast_to(elevation_factor, shape) # Filter wavelength within dataset range. wavelength_end = dataset.channel_start + dataset.channels * dataset.channel_width valid = (wavelength >= dataset.channel_start) & (wavelength < wavelength_end) if not np.any(valid): return np.zeros((*shape, wavelength_count), dtype=np.float64) channel_indices = as_int_array( np.floor((wavelength[valid] - dataset.channel_start) / dataset.channel_width) ) n_valid = len(channel_indices) # Precompute strides for offset calculation. elevation_count = len(dataset.elevations_radiance) altitude_count = len(dataset.altitudes_radiance) albedo_count = len(dataset.albedos_radiance) total_coefficients_single = metadata.total_coefficients_single_configuration # 4D hypercube corners over (visibility, albedo, altitude, elevation). grid_shape = (16, *shape, n_valid) grid = np.zeros(grid_shape, dtype=np.float64) for i in range(16): visibility_grid_index = np.minimum( visibility_index + i // 8, len(dataset.visibilities_radiance) - 1 ) albedo_grid_index = np.minimum(albedo_index + (i % 8) // 4, albedo_count - 1) altitude_grid_index = np.minimum( altitude_index + (i % 4) // 2, altitude_count - 1 ) elevation_grid_index = np.minimum(elevation_index + i % 2, elevation_count - 1) offsets = total_coefficients_single * ( channel_indices + dataset.channels * elevation_grid_index[..., None] + dataset.channels * elevation_count * altitude_grid_index[..., None] + dataset.channels * elevation_count * altitude_count * albedo_grid_index[..., None] + dataset.channels * elevation_count * altitude_count * albedo_count * visibility_grid_index[..., None] ) grid[i] = _reconstruct_sky_model( data, offsets, gamma_index, gamma_factor, alpha_index, alpha_factor, zero_index, zero_factor, metadata, ) # 4-level hierarchical interpolation (elevation, altitude, albedo, # visibility). factors = [elevation_factor, altitude_factor, albedo_factor, visibility_factor] for level_factor in factors: low = grid[0::2] high = grid[1::2] grid = low + level_factor[..., None] * (high - low) result_valid = grid[0] # Place valid wavelength into full result. result = np.zeros((*shape, wavelength_count), dtype=np.float64) result[..., valid] = result_valid return result
[docs] def sky_radiance_Wilkie2021( dataset: SkyDataset_Wilkie2021, parameters: SkyParameters_Wilkie2021, wavelength: ArrayLike, ) -> NDArrayFloat: """ Compute sky radiance (without direct sun) for given wavelength. Parameters ---------- dataset Loaded dataset from :func:`load_dataset_Wilkie2021`. parameters Sky parameters from :func:`compute_sky_parameters_Wilkie2021`. wavelength Wavelengths in nanometers. Returns ------- :class:`numpy.ndarray` Sky radiance values. References ---------- :cite:`Wilkie2021`, :cite:`Vevoda2022` """ return _evaluate_sky_model( dataset, parameters, as_float_array(wavelength), dataset.data_radiance, dataset.metadata_radiance, )
[docs] def sun_radiance_Wilkie2021( dataset: SkyDataset_Wilkie2021, parameters: SkyParameters_Wilkie2021, wavelength: ArrayLike, ) -> NDArrayFloat: """ Compute sun radiance (without inscattered sky contribution) for given wavelength. Parameters ---------- dataset Loaded dataset from :func:`load_dataset_Wilkie2021`. parameters Sky parameters from :func:`compute_sky_parameters_Wilkie2021`. wavelength Wavelengths in nanometers. Returns ------- :class:`numpy.ndarray` Sun radiance values (zero for directions not hitting the sun). References ---------- :cite:`Wilkie2021`, :cite:`Vevoda2022` """ wavelength = as_float_array(wavelength) gamma = as_float_array(parameters.gamma) shape = np.shape(gamma) wavelength_count = len(wavelength) result = np.zeros((*shape, wavelength_count), dtype=np.float64) # Mask: only directions hitting the sun disk. hits_sun = gamma <= CONSTANTS_WILKIE2021.sun_radius if not np.any(hits_sun): return result valid_wavelength = (wavelength >= CONSTANTS_WILKIE2021.sun_radiance_start) & ( wavelength < CONSTANTS_WILKIE2021.sun_radiance_end ) if not np.any(valid_wavelength): return result # Interpolate solar radiance from table. index_float = ( wavelength[valid_wavelength] - CONSTANTS_WILKIE2021.sun_radiance_start ) / CONSTANTS_WILKIE2021.sun_radiance_step index_integer = as_int_array(np.floor(index_float)) index_fraction = index_float - index_integer index_integer = np.clip(index_integer, 0, len(SUN_RAD_TABLE) - 2) sun_radiance_value = ( SUN_RAD_TABLE[index_integer] * (1.0 - index_fraction) + SUN_RAD_TABLE[index_integer + 1] * index_fraction ) # Compute transmittance towards the sun. transmittance = sky_transmittance_Wilkie2021( dataset, parameters, wavelength[valid_wavelength], np.inf ) result[..., valid_wavelength] = sun_radiance_value * transmittance result *= hits_sun[..., None] return result
[docs] def sky_polarisation_Wilkie2021( dataset: SkyDataset_Wilkie2021, parameters: SkyParameters_Wilkie2021, wavelength: ArrayLike, ) -> NDArrayFloat: """ Compute degree of polarisation for given wavelength. Parameters ---------- dataset Loaded dataset from :func:`load_dataset_Wilkie2021`. parameters Sky parameters from :func:`compute_sky_parameters_Wilkie2021`. wavelength Wavelengths in nanometers. Returns ------- :class:`numpy.ndarray` Degree of polarisation. The result is negated from the internal model coefficients following the convention of the reference C++ implementation. Raises ------ ValueError If the dataset does not contain polarisation data. References ---------- :cite:`Wilkie2021`, :cite:`Vevoda2022` """ if dataset.metadata_polarisation.rank == 0: message = "The supplied dataset does not contain polarisation data." raise ValueError(message) return -_evaluate_sky_model( dataset, parameters, as_float_array(wavelength), dataset.data_polarisation, dataset.metadata_polarisation, )
def _compute_transmittance_interpolation( value: NDArrayFloat, count: int, power: int ) -> tuple[NDArrayFloat, NDArrayFloat]: """Compute transmittance-specific interpolation index and factor.""" value = as_float_array(value) index = np.minimum(as_int_array(value * count), count - 1) lower = index / count upper = (index + 1) / count denominator = upper**power - lower**power factor = np.where( (index < count - 1) & (denominator != 0), np.clip( (value**power - lower**power) / np.where(denominator == 0, 1.0, denominator), 0.0, 1.0, ), 0.0, ) return index, factor def _compute_transmittance_parameters( dataset: SkyDataset_Wilkie2021, theta: ArrayLike, distance: float, altitude: ArrayLike, ) -> tuple[tuple[NDArrayFloat, NDArrayFloat], tuple[NDArrayFloat, NDArrayFloat]]: """Convert viewing geometry to transmittance interpolation parameters.""" theta = as_float_array(theta) altitude = as_float_array(altitude) ray_direction_x = np.sin(theta) ray_direction_y = np.cos(theta) ray_position_y = CONSTANTS_WILKIE2021.planet_radius + altitude shape = np.broadcast_shapes(np.shape(theta), np.shape(altitude)) ray_origin = np.stack( [np.broadcast_to(0.0, shape), np.broadcast_to(ray_position_y, shape)], axis=-1, ) ray_direction = np.stack( [ np.broadcast_to(ray_direction_x, shape), np.broadcast_to(ray_direction_y, shape), ], axis=-1, ) atmosphere_edge = ( CONSTANTS_WILKIE2021.planet_radius + CONSTANTS_WILKIE2021.atmosphere_width ) is_low_altitude = altitude < CONSTANTS_WILKIE2021.low_altitude_threshold # Low altitude: atmosphere edge if theta <= pi/2, else 0. distance_low_atmosphere = intersect_ray_circle_2d( ray_origin, ray_direction, atmosphere_edge ) distance_low = np.where(theta <= 0.5 * np.pi, distance_low_atmosphere, 0.0) # High altitude: planet first, atmosphere edge if planet missed. distance_planet = intersect_ray_circle_2d( ray_origin, ray_direction, CONSTANTS_WILKIE2021.planet_radius ) distance_atmosphere = intersect_ray_circle_2d( ray_origin, ray_direction, atmosphere_edge ) distance_high = np.where( np.isnan(distance_planet), distance_atmosphere, distance_planet ) distance_to_intersection = np.where(is_low_altitude, distance_low, distance_high) distance_to_intersection = np.minimum(distance_to_intersection, distance) intersection_x = ray_direction_x * distance_to_intersection intersection_y = ray_direction_y * distance_to_intersection + ray_position_y intersection_distance = np.sqrt( intersection_x * intersection_x + intersection_y * intersection_y ) altitude_parameter = np.clip( intersection_distance - CONSTANTS_WILKIE2021.planet_radius, 0.0, CONSTANTS_WILKIE2021.atmosphere_width, ) altitude_parameter = ( altitude_parameter / CONSTANTS_WILKIE2021.atmosphere_width ) ** (1.0 / 3.0) distance_parameter = ( np.arccos( np.clip( intersection_y / np.maximum( intersection_distance, CONSTANTS_WILKIE2021.distance_epsilon ), -1, 1, ) ) * CONSTANTS_WILKIE2021.planet_radius ) distance_parameter = np.sqrt( distance_parameter / CONSTANTS_WILKIE2021.distance_to_edge ) distance_parameter = np.sqrt(distance_parameter) distance_parameter = np.minimum(1.0, distance_parameter) altitude_interpolation = _compute_transmittance_interpolation( altitude_parameter, dataset.altitude_dimension, 3 ) distance_interpolation = _compute_transmittance_interpolation( distance_parameter, dataset.distance_dimension, 4 ) return altitude_interpolation, distance_interpolation def _reconstruct_transmittance( dataset: SkyDataset_Wilkie2021, visibility_index: NDArrayReal, altitude_index: NDArrayReal, altitude_interpolation: tuple[NDArrayReal, NDArrayReal], distance_interpolation: tuple[NDArrayReal, NDArrayReal], channel_indices: NDArrayReal, ) -> NDArrayFloat: """ Reconstruct transmittance for multiple channels. If *visibility_index* and *altitude_index* have shape ``(*S,)`` and *channel_indices* has shape ``(W,)``, the result has shape ``(*S, W)``. """ altitude_i, altitude_f = altitude_interpolation distance_i, distance_f = distance_interpolation altitude_d = dataset.altitude_dimension distance_d = dataset.distance_dimension rank = dataset.rank_transmittance altitude_transmittance_count = len(dataset.altitudes_transmittance) # V-coefficients offset: (*S, W, rank) v_coefficient_base = ( ( visibility_index[..., None] * altitude_transmittance_count + altitude_index[..., None] ) * dataset.channels + channel_indices ) * rank v_coefficient_offsets = v_coefficient_base[..., None] + np.arange(rank) v_coefficients = as_float_array( dataset.data_transmittance_v[as_int_array(v_coefficient_offsets)] ) # U-coefficients: iterate 2x2 grid (altitude x distance). transmittance = np.zeros( (*np.shape(altitude_i), len(channel_indices), 4), dtype=np.float64 ) grid_index = 0 for altitude_offset in range(2): a = altitude_i + altitude_offset altitude_valid = a < altitude_d for distance_offset in range(2): d = distance_i + distance_offset distance_valid = d < distance_d u_coefficient_base = ( altitude_index[..., None] * altitude_d * distance_d * rank + (d[..., None] * altitude_d + a[..., None]) * rank ) u_coefficient_offsets = u_coefficient_base[..., None] + np.arange(rank) safe_u_offsets = np.clip( u_coefficient_offsets, 0, len(dataset.data_transmittance_u) - 1 ) u_coefficients = as_float_array( dataset.data_transmittance_u[as_int_array(safe_u_offsets)] ) dot = np.sum(u_coefficients * v_coefficients, axis=-1) mask = altitude_valid & distance_valid transmittance[..., grid_index] = np.where(mask[..., None], dot, 0.0) grid_index += 1 # Bilinear interpolation over distance then altitude. low = lerp(distance_f[..., None], transmittance[..., 0], transmittance[..., 1]) high = lerp(distance_f[..., None], transmittance[..., 2], transmittance[..., 3]) low = np.maximum(low, 0.0) high = np.maximum(high, 0.0) return lerp(altitude_f[..., None], low, high)
[docs] def sky_transmittance_Wilkie2021( dataset: SkyDataset_Wilkie2021, parameters: SkyParameters_Wilkie2021, wavelength: ArrayLike, distance: float = np.inf, ) -> NDArrayFloat: """ Compute atmospheric transmittance for given wavelength and distance. Parameters ---------- dataset Loaded dataset. parameters Sky parameters from :func:`compute_sky_parameters_Wilkie2021`. wavelength Wavelengths in nanometers. distance Distance along view direction in meters (``np.inf`` for infinity). Returns ------- :class:`numpy.ndarray` Transmittance values [0, 1]. References ---------- :cite:`Wilkie2021`, :cite:`Vevoda2022` """ wavelength = as_float_array(wavelength) wavelength_count = len(wavelength) theta = as_float_array(parameters.theta) shape = np.shape(theta) wavelength_end = dataset.channel_start + dataset.channels * dataset.channel_width valid = (wavelength >= dataset.channel_start) & (wavelength < wavelength_end) if not np.any(valid): return np.zeros((*shape, wavelength_count), dtype=np.float64) channel_indices = as_int_array( np.floor((wavelength[valid] - dataset.channel_start) / dataset.channel_width) ) visibility_index, visibility_factor = linear_interpolation_index_and_factor( parameters.visibility, dataset.visibilities_transmittance ) altitude_index, altitude_factor = linear_interpolation_index_and_factor( parameters.altitude, dataset.altitudes_transmittance ) # Broadcast config indices to common shape. shape = np.broadcast_shapes( np.shape(theta), np.shape(visibility_index), np.shape(altitude_index), ) visibility_index = np.broadcast_to(visibility_index, shape) visibility_factor = np.broadcast_to(visibility_factor, shape) altitude_index = np.broadcast_to(altitude_index, shape) altitude_factor = np.broadcast_to(altitude_factor, shape) altitude_interpolation, distance_interpolation = _compute_transmittance_parameters( dataset, parameters.theta, distance, parameters.altitude ) # 4-point interpolation over visibility x altitude. transmittance = _reconstruct_transmittance( dataset, visibility_index, altitude_index, altitude_interpolation, distance_interpolation, channel_indices, ) transmittance_altitude_high = _reconstruct_transmittance( dataset, visibility_index, np.minimum(altitude_index + 1, len(dataset.altitudes_transmittance) - 1), altitude_interpolation, distance_interpolation, channel_indices, ) transmittance = lerp( altitude_factor[..., None], transmittance, transmittance_altitude_high ) transmittance_visibility_high = _reconstruct_transmittance( dataset, np.minimum(visibility_index + 1, len(dataset.visibilities_transmittance) - 1), altitude_index, altitude_interpolation, distance_interpolation, channel_indices, ) transmittance_visibility_altitude_high = _reconstruct_transmittance( dataset, np.minimum(visibility_index + 1, len(dataset.visibilities_transmittance) - 1), np.minimum(altitude_index + 1, len(dataset.altitudes_transmittance) - 1), altitude_interpolation, distance_interpolation, channel_indices, ) transmittance_visibility_high = lerp( altitude_factor[..., None], transmittance_visibility_high, transmittance_visibility_altitude_high, ) transmittance = lerp( visibility_factor[..., None], transmittance, transmittance_visibility_high ) # Transmittance is stored as square root. transmittance = transmittance * transmittance transmittance = np.clip(transmittance, 0.0, 1.0) result = np.zeros((*shape, wavelength_count), dtype=np.float64) result[..., valid] = transmittance return result