Source code for solcore.light_source.smarts

import numpy as np
import os
import shutil
import subprocess
import tempfile
import platform
import solcore
from datetime import datetime


[docs]class SmartsSolverError(Exception): pass
smarts = solcore.config.smarts() system = platform.system() if system == 'Windows': extension = '.exe' elif system == 'Linux': extension = '' else: extension = '.command' executable = os.path.join(smarts, "smarts295" + extension) error_msg = """ERROR: SMARTS location not correctly configured or SMARTS executable not working. SMARTS can be obtained free of charge from the NREL webpage: http://www.nrel.gov/rredc/smarts/ You might need to re-compile the Fortran code as current 64 bit CPUs are not supported by the shipped binaries. """
[docs]def skipper(fname): with open(fname) as fin: no_comments = (line for line in fin if " " in line) next(no_comments, None) # skip header next(no_comments, None) # skip header for row in no_comments: yield row
[docs]def calculate_spectrum_smarts(smarts_file_contents=None, filename='smarts295', target_directory=None): """ :param smarts_file_contents: :param filename: :param target_directory: :return: """ if not smarts: raise SmartsSolverError(f"Smarts installation not found in {smarts}") if smarts_file_contents is None: smarts_file_contents = build_smarts_file(**get_default_smarts_object()) else: smarts_file_contents = build_smarts_file(**smarts_file_contents) if target_directory is not None: target_directory = target_directory.rstrip('\/') assert os.access(target_directory, os.W_OK), 'ERROR: Target folder for smarts output does not exists ' \ 'or is not writable.' data = [] # We use a temp directory to store temporary the data. # If needed, we'll copy it later to the target directory. with tempfile.TemporaryDirectory(prefix="tmp", suffix="_sc3SMARTS") as working_directory: ext_file = os.path.join(working_directory, "{}.ext.txt".format(filename)) inp_file = os.path.join(working_directory, "{}.inp.txt".format(filename)) out_file = os.path.join(working_directory, "{}.out.txt".format(filename)) scn_file = os.path.join(working_directory, "{}.scn.txt".format(filename)) # Save the data to the input file with open(inp_file, "w") as f: f.write(smarts_file_contents) # Start the process try: this_process = subprocess.Popen((executable,), stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, cwd=smarts) # We need to tell smarts where to find the input data output, error = this_process.communicate( input=bytes('N\n"{0}"\n{1}\nY\n'.format(working_directory, filename), "ASCII")) error = error.decode('utf8') if len(error) > 0 and 'Note' not in error: raise RuntimeError(error) data = [] if os.path.exists(scn_file): data = np.loadtxt(skipper(scn_file), unpack=True) if target_directory is not None: shutil.copy2(scn_file, scn_file.replace(working_directory, target_directory)) if os.path.exists(inp_file): if target_directory is not None: shutil.copy2(inp_file, inp_file.replace(working_directory, target_directory)) if os.path.exists(ext_file): if target_directory is not None: shutil.copy2(ext_file, ext_file.replace(working_directory, target_directory)) if os.path.exists(out_file): if target_directory is not None: shutil.copy2(out_file, out_file.replace(working_directory, target_directory)) except RuntimeError as err: print('ERROR in SMARTS: {}'.format(err)) except ValueError as err: print('ERROR in SMARTS: {}'.format(err)) except Exception as err: print('ERROR in SMARTS: {}'.format(error_msg)) print('ERROR in SMARTS: {}'.format(err)) if len(data) == 0: raise ValueError('ERROR in SMARTS: Output file is empty. Likely error in the input parameters.') return data
[docs]def build_smarts_file(**kwargs): try: smarts_file_contents = ["'{COMNT}' !Card 1"] smarts_file_contents.append("{ISPR} !Card 2") smarts_file_contents.append([ "{SPR} !Card 2a", "{SPR} {ALTIT} {HEIGHT} !Card 2a", "{LATIT} {ALTIT} {HEIGHT} !Card 2a" ][kwargs["ISPR"]]) smarts_file_contents.append("{IATMOS} !Card 3") smarts_file_contents.append([ "{TAIR} {RH} '{SEASON}' {TDAY} !Card 3a", "'{ATMOS}' !Card 3a" ][kwargs["IATMOS"]]) smarts_file_contents.append("{IH2O} !Card 4") if kwargs["IH2O"] == 0: smarts_file_contents.append("{W} !Card 4a") smarts_file_contents.append("{IO3} !Card 5") if kwargs["IO3"] == 0: smarts_file_contents.append("{IALT} {AbO3} !Card 5a") smarts_file_contents.append("{IGAS} !Card 6") if kwargs["IGAS"] == 0: smarts_file_contents.append("{ILOAD} !Card 6a") if kwargs["ILOAD"] == 0: smarts_file_contents.append( "{ApCH2O} {ApCH4} {ApCO} {ApHNO2} {ApHNO3} {ApNO} {ApNO2} {ApNO3} {ApO3} {ApSO2} !Card 6b" ) smarts_file_contents.append("{qCO2} !Card 7") smarts_file_contents.append("{ISPCTR} !Card 7a") smarts_file_contents.append("'{AEROS}' !Card 8") if kwargs["AEROS"] == "USER": smarts_file_contents.append("{ALPHA1} {ALPHA2} {OMEGL} {GG} !Card 8a") smarts_file_contents.append("{ITURB} !Card 9") smarts_file_contents.append([ "{TAU5} !Card 9a", "{BETA} !Card 9a", "{BCHUEP} !Card 9a", "{RANGE} !Card 9a", "{VISI} !Card 9a", "{TAU550} !Card 9a", ][kwargs["ITURB"]]) smarts_file_contents.append("{IALBDX} !Card 10") if kwargs["IALBDX"] == -1: smarts_file_contents.append("{RHOX} !Card 10a") smarts_file_contents.append("{ITILT} !Card 10b") if kwargs["ITILT"] == 1: smarts_file_contents.append("{IALBDG} {TILT} {WAZIM} !Card 10c") if kwargs["IALBDG"] == 1: smarts_file_contents.append("{RHOG} !Card 10d") smarts_file_contents.append("{WLMN} {WLMX} {SUNCOR} {SOLARC} !Card 11") smarts_file_contents.append("{IPRT} !Card 12") if kwargs["IPRT"] >= 1: smarts_file_contents.append("{WPMN} {WPMX} {INTVL} !Card 12a") if kwargs["IPRT"] == 2 or kwargs["IPRT"] == 3: smarts_file_contents.append("{IOTOT} !Card 12b") smarts_file_contents.append("{IOUT} !Card 12c") smarts_file_contents.append("{ICIRC} !Card 13") if kwargs["ICIRC"] == 1: smarts_file_contents.append("{SLOPE} {APERT} {LIMIT} !Card 13a") smarts_file_contents.append("{ISCAN} !Card 14") if kwargs["ISCAN"] == 1: smarts_file_contents.append("{IFILT} {WV1} {WV2} {STEP} {FWHM} !Card 14a") smarts_file_contents.append("{ILLUM} !Card 15") smarts_file_contents.append("{IUV} !Card 16") smarts_file_contents.append("{IMASS} !Card 17") smarts_file_contents.append([ "{ZENIT} {AZIM} !Card 17a", "{ELEV} {AZIM} !Card 17a", "{AMASS} !Card 17a", "{YEAR} {MONTH} {DAY} {HOUR} {LATIT} {LONGIT} {ZONE} !Card 17a", "{MONTH} {LATIT} {DSTEP} !Card 17a", ][kwargs["IMASS"]]) smarts_file_schema = "\n".join(smarts_file_contents) # print (smarts_file_schema) smarts_file_complete = smarts_file_schema.format(**kwargs) + "\n" except KeyError as err: print("The SMARTS options you have selected require additional data, variables are undefined.") print(err) raise return smarts_file_complete
[docs]def get_default_smarts_object(): """ Provides a dictionary with most of the parameters (values of the CARDS) needed by SMARTS to calculate a solar spectrum. It can be used as a template to customize for user defined conditions. :return: """ # CONSTANT PARAMETERS --------------------------------- # location and general time info latitude = 40.4966 # 'LATIT', deg, latitude longitude = -3.4620 # 'LONGIT, deg, longitude preasure_model = 1 # 'ISPR', surface preasure model set to real data + altitude correction altitude = 0.625 # 'ALTIT', km, altitude above sea level altura = 0.0 # 'HEIGHT', m, altude over the ground level time_zone = 0 # 'ZONE', time zone season = 'SUMMER' # 'SEASON', season of the year. Can be summer or winter only albedo = 9 # 'IALBDX', 'IALBDG', Albedo model = 9, Dry clay soil solar_position_mode = 3 # 'IMASS', use the location and time to calculate the position of the sun # atmospheric conditions atmospheric_data = 1 # 'IATMOS', allows to input the correct atmospheric data atmosphere_model = 'USSA' # 'ATMOS', 'US standard spectrum', general atmospheric model, setting all parameters not given as input precipitable_water = 0 # 'IH2O', water vapor data given as input ozone = 1 # 'IO3', ozone abundance calculated from atmospheric data gas_contents = 0 # 'IGAS', gas abundances set to a particular scenario gas_scenairo = 2 # 'ILOAD', light pollution scenario CO2content = 370 # 'qCO2', CO2 content in ppmv extraterrestial = 0 # 'ISPCTR', extraterrestial spectrum aerosol = 'S&F_RURAL' # 'AEROS', aerosol model turbidity = 0 # 'ITURB', select turbidity model tau500_param = 0.085 # 'TAU5' # output info print_info = 2 # 'IPRT', print output in spreadsheet format total_variables = 1 # 'IOTOT', total number of variables to print which_variables = '4' # 'IOUT', code of the output variables. Set to all tilted irradiances + experimental with FoV wavelenght_min = 280 # 'WLMN', 'WPMN', nm wavelenght_max = 4004 # 'WLMX', 'WPMX', nm wavelenght_step = 0.5 # 'INTVL', nm convolute = 1 # 'ISCAN' conv_function = 1 # 'IFILT', Gausian conv_wl_min = 300 # 'WV1', nm conv_wl_max = 3990 # 'WV2', nm conv_FWHM = 4 # 'FWHM', nm conv_step = 2 # 'STEP', nm tilt = 1 # 'ITILT', enable calculations for a tilt surface altitude_tilt = -999 # 'TILT', -999 means 'track the Sun' azimuth_tilt = -999 # 'WAZIM', -999 means 'track the Sun' solar_constant = 1367 # 'SOLARC, W/m2, solar constant sun_correction = 1 # 'SUNCOR', distance to the Sun correction faction. It is not used # Colimator information circumsolar = 1 # 'ICIRC', if circumsolar radiation is to be calculated slope = 1 # 'SLOPE', configuration of the colimator aperture = 2.5 # 'APERT' limit = 4 # 'LIMIT' # Others illuminance = 0 # 'ILLUM' UVcalc = 0 # 'IUV' air_mass = 3 # 'IMASS', set to calculate the air mass from the time and location # VARIABLES --------------------------------- comment = 'Test' # 'COMNT', single line with a comment on the run. Max 64 characters, no spaces but underscores P = 940 # 'SPR', mb, surface preasure T_air = 17 # 'TAIR', deg, temperature of the air T_day = 25 # 'TDAY', deg, average daily temperature humid = 30 # 'RH', %, relative humidity water_vapour = 1 # 'W', cm, precipitable water targetTime = datetime(2015, 5, 19, 12, 30) # has to be split in 'YEAR', 'MONTH', 'DAY', 'HOUR', the later in Local Standard Time smarts_input = { 'LATIT': latitude, 'LONGIT': longitude, 'ISPR': preasure_model, 'ALTIT': altitude, 'HEIGHT': altura, 'ZONE': time_zone, 'SEASON': season, 'IALBDX': albedo, 'IALBDG': albedo, 'IMASS': solar_position_mode, 'IATMOS': atmospheric_data, 'ATMOS': atmosphere_model, 'IH2O': precipitable_water, 'IO3': ozone, 'IGAS': gas_contents, 'ILOAD': gas_scenairo, 'qCO2': CO2content, 'ISPCTR': extraterrestial, 'AEROS': aerosol, 'ITURB': turbidity, 'TAU5': tau500_param, 'IPRT': print_info, 'IOTOT': total_variables, 'IOUT': which_variables, 'WLMN': wavelenght_min, 'WPMN': wavelenght_min, 'WLMX': wavelenght_max, 'WPMX': wavelenght_max, 'INTVL': wavelenght_step, 'ISCAN': convolute, 'IFILT': conv_function, 'WV1': conv_wl_min, 'WV2': conv_wl_max, 'FWHM': conv_FWHM, 'STEP': conv_step, 'ITILT': tilt, 'TILT': altitude_tilt, 'WAZIM': azimuth_tilt, 'SOLARC': solar_constant, 'SUNCOR': sun_correction, 'ICIRC': circumsolar, 'SLOPE': slope, 'APERT': aperture, 'LIMIT': limit, 'ILLUM': illuminance, 'IUV': UVcalc, 'COMNT': comment, 'SPR': P, 'TAIR': T_air, 'TDAY': T_day, 'RH': humid, 'W': water_vapour, 'YEAR': targetTime.year, 'MONTH': targetTime.month, 'DAY': targetTime.day, 'HOUR': targetTime.hour + (targetTime.minute + targetTime.second / 60) / 60 } return smarts_input
if __name__ == "__main__": data = calculate_spectrum_smarts(target_directory='/Users/diego/Downloads') import matplotlib.pyplot as plt plt.plot(data[0], data[1]) plt.plot(data[0], data[2]) plt.plot(data[0], data[3]) plt.plot(data[0], data[4]) plt.show()