"""Parse to and from a JSON string/file format for isotherms.The _parser_version variable documents any changes to the format,and is used to check for any deprecations."""importjsonimportpandasfrompygapsimportloggerfrompygaps.core.baseisothermimportBaseIsothermfrompygaps.core.materialimportMaterialfrompygaps.core.modelisothermimportModelIsothermfrompygaps.core.pointisothermimportPointIsothermfrompygaps.modellingimportmodel_from_dictfrompygaps.units.converter_modeimport_MASS_UNITSfrompygaps.units.converter_modeimport_MOLAR_UNITSfrompygaps.units.converter_modeimport_PRESSURE_UNITSfrompygaps.units.converter_modeimport_VOLUME_UNITSfrompygaps.utilities.exceptionsimportParsingError_parser_version="3.0"
[docs]defisotherm_to_json(isotherm,path=None,**args_to_json):""" Convert an isotherm object to a json representation. If the path is specified, the isotherm is saved as a file, otherwise it is returned as a string. Structure is inspired by the NIST format. Parameters ---------- isotherm : Isotherm Isotherm to be written to json. path : str, None Path to the file to be written. args_to_json : dict Custom arguments to be passed to "json.dump". Returns ------- None or str If path is None, returns the resulting json format as a string. Otherwise returns None. """# Isotherm propertiesiso_dict=isotherm.to_dict()iso_dict['file_version']=_parser_version# version# Isotherm dataifisinstance(isotherm,PointIsotherm):# we turn the raw dataframe into a dictionaryisotherm_data_dict=isotherm.data_raw.to_dict(orient='index')defprocess_data(value):""" Specifically mark only the desorption branch. """ifvalue.get('branch',0)==0:delvalue['branch']else:value['branch']='des'returnvalueiso_dict["isotherm_data"]=[process_data(v)forvinisotherm_data_dict.values()]elifisinstance(isotherm,ModelIsotherm):iso_dict["isotherm_model"]=isotherm.model.to_dict()args_to_json={}ifargs_to_jsonisNoneelseargs_to_jsonargs_to_json['sort_keys']=True# we will sort alwaysifpath:withopen(path,mode='w',encoding='utf-8')asfile:json.dump(iso_dict,file,**args_to_json)else:returnjson.dumps(iso_dict,**args_to_json)
[docs]defisotherm_from_json(str_or_path,fmt=None,loading_key='loading',pressure_key='pressure',**isotherm_parameters,):""" Read a pyGAPS isotherm from a file or from a string. Structure is inspired by the NIST format. Parameters ---------- str_or_path : str The isotherm in a json string format or a path to where one can be read. loading_key : str The title of the pressure data in the json provided. pressure_key The title of the loading data in the json provided. fmt : {None, 'NIST'}, optional If the format is set to NIST, then the json format a specific version used by the NIST database of adsorbents. Other Parameters ---------------- isotherm_parameters : Any other options to be overridden in the isotherm creation. Returns ------- Isotherm The isotherm contained in the json string or file. """# Parse isotherm in dictionarytry:withopen(str_or_path,encoding='utf-8')asf:raw_dict=json.load(f)exceptOSError:try:raw_dict=json.loads(str_or_path)exceptExceptionaserr:raiseParsingError("Could not parse JSON isotherm. ""The `str_or_path` is invalid or does not exist. ")fromerr# version checkversion=raw_dict.pop("file_version",None)ifnotversionorfloat(version)<float(_parser_version):logger.warning(f"The file version is {version} while the parser uses version {_parser_version}. ""Strange things might happen, so double check your data.")# Parse materialif'material'inisotherm_parameters:material=isotherm_parameters['material']ifisinstance(material,dict):isotherm_parameters['material']=Material(material)# Update dictionary with any user parametersraw_dict.update(isotherm_parameters)data=raw_dict.pop("isotherm_data",None)model=raw_dict.pop("isotherm_model",None)ifdata:# rename keys and get units if needed depending on formatiffmt=='NIST':loading_key='total_adsorption'pressure_key='pressure'raw_dict=_from_json_nist(raw_dict)data=_from_data_nist(data)# build pandas dataframe of datadata=pandas.DataFrame.from_dict(data)# process isotherm branches if they existif'branch'indata.columns:data['branch']=data['branch'].fillna('0').replace({'ads':'0','des':'1'}).astype(int)else:raw_dict['branch']='guess'# generate the isothermisotherm=PointIsotherm(isotherm_data=data,loading_key=loading_key,pressure_key=pressure_key,**raw_dict,)elifmodel:# generate the isothermisotherm=ModelIsotherm(model=model_from_dict(model),**raw_dict,)else:# generate the isothermisotherm=BaseIsotherm(**raw_dict)returnisotherm
def_from_json_nist(raw_dict):"""Convert a NIST dictionary format to an internal format."""nist_dict={}# Get regular isotherm parametersnist_dict['material']=raw_dict['adsorbent']['name']nist_dict['nist_hash']=raw_dict.pop('adsorbent')['hashkey']nist_dict['temperature']=raw_dict.pop('temperature')# Get adsorbateiflen(raw_dict['adsorbates'])>1:raiseParsingError('Cannot currently read multicomponent isotherms')nist_dict['adsorbate']=raw_dict.pop('adsorbates')[0]['name'].lower()# Get loading basis and unitloading_string=raw_dict.pop("adsorptionUnits")comp=loading_string.split('/')iflen(comp)!=2:ifcomp[0]=='wt%':comp=('g','g')else:raiseParsingError("Isotherm cannot be parsed due to loading string format.")loading_unit=comp[0]ifloading_unitin_MOLAR_UNITS:loading_basis='molar'elifloading_unitin_MASS_UNITS:loading_basis='mass'elifloading_unitin_VOLUME_UNITS:loading_basis='volume'else:raiseParsingError("Isotherm cannot be parsed due to loading unit.")material_unit=comp[1]ifmaterial_unitin_MASS_UNITS:material_basis="mass"elifmaterial_unitin_VOLUME_UNITS:material_basis="volume"elifmaterial_unitin_MOLAR_UNITS:material_basis="molar"else:raiseParsingError("Isotherm cannot be parsed due to material basis.")# Get pressure mode and unitpressure_mode="absolute"pressure_string=raw_dict.pop("pressureUnits")ifpressure_stringin_PRESSURE_UNITS:pressure_unit=pressure_stringelse:raiseParsingError("Isotherm cannot be parsed due to pressure unit.")# Add all the rest of the parametersnist_dict['iso_type']=raw_dict.pop('category')# exp/sim/mod/quanist_dict['iso_ref']=raw_dict.pop('isotherm_type')# excess/absolutenist_dict.update(raw_dict)nist_dict.update({'material_basis':material_basis,'material_unit':material_unit,'loading_basis':loading_basis,'loading_unit':loading_unit,'pressure_mode':pressure_mode,'pressure_unit':pressure_unit,})returnnist_dictdef_from_data_nist(data_raw):"""Convert a NIST data format to an internal format."""forpointindata_raw:point.pop('species_data')returndata_raw