pauliacomi / pygaps Goto Github PK
View Code? Open in Web Editor NEWA framework for processing adsorption data and isotherm fitting
License: MIT License
A framework for processing adsorption data and isotherm fitting
License: MIT License
It looks like 3P changed something about the format of their .xlsx files, which made it fail when I tried to load my files.
I'm using Version 10.03.09.04 of their analysis software
3P_report_file.xlsx
Since I saw the bath Temperature on the TO-DO List:
it's stored in 3P's own .jwgbt file format, but when exporting to xlsx that data is not stored.
Here is a report file in both formats:
3P_report_file.txt (change extension from .jwgbt)
3P_report_file.xlsx
When generating the RSA hashes on different machines, they produce different results.
Likely an issue due to different floating point conversions when generating the JSON.
The initial_henry_slope
seems to be ignoring the dataset and favoring a henry coefficient of ~0 mmol/(g-bar) rather than trying to fit it to the data in some cases.
I tried running it on a few adsorption isotherms from the NIST adsorption dataset and ran into this problem for a few of them.
Here's a code snippet to reproduce this:
import pygaps
import requests
import json
import numpy as np
host = "adsorption.nist.gov"
url = "https://adsorption.nist.gov/isodb/api/isotherm/10.1021La803074g.isotherm3b.json"
isotherm_data = json.loads(requests.get(url).content)
pressure = [datapoint['pressure'] for datapoint in isotherm_data['isotherm_data']]
adsorption = [datapoint['species_data'][0]['adsorption'] for datapoint in isotherm_data['isotherm_data']]
isotherm = pygaps.PointIsotherm(
pressure=pressure,
loading=adsorption,
material_name=isotherm_data['adsorbent']['name'],
material_batch='',
t_iso=isotherm_data['temperature'],
adsorbate=isotherm_data['adsorbates'][0]['name'])
henry_coeff = pygaps.initial_henry_slope(isotherm, verbose=True)
Note I'm running pyGAPS version 1.6.1 on Python 3.7.3 and the above code was ran through using Jupyter Lab (using a virtualenv) on Ubuntu 16.04 LTS
I've taken a look at the code, and it looks to me that when the Henry fit isn't good enough, it should default to a fit between the first 2 points rather than flatlining.
Thank you,
-Árni
some apparatuses include a saturation pressure measurement for each isotherm point
ensure this is accounted for in internal logic and parsing
Allow user to specify if PointIsotherms should be attempted to be split into adsorption/desorption branches.
Describe the bug
I think Dubinin volume calculations are incorrect; they are overestimated due to the order of the calculations.
To Reproduce
See dr_da_plots:
line 223
pressure, loading = get_iso_loading_and_pressure_ordered(
isotherm, branch, {
"loading_basis": "molar",
"loading_unit": "mol"
}, {"pressure_mode": "relative"}
)
and
line 403
def log_v_adj(loading, molar_mass, liquid_density):
"""Base 10 logarithm of volumetric uptake."""
return numpy.log10(loading * molar_mass / liquid_density)
I think that this is trying to combine the calculation of limiting micropore capacity and limiting micropore volume with the log(V) variable. This results in incorrect (negative) values for log(V).
System (please complete the following information):
Additional context
I am working on a patch for my own use which first takes the loading in cm3
pressure, loading = get_iso_loading_and_pressure_ordered(
isotherm, branch, {
"loading_basis": "volume_gas",
"loading_unit": "cm3"
}, {"pressure_mode": "relative"}
)
Then takes just takes the logarithm of the loading;
def log_v_adj(loading, molar_mass, liquid_density):
"""Base 10 logarithm of volumetric uptake."""
return numpy.log10(loading)
This means that the limiting micropore capacity is calculated instead of micropore volume
This is converted to micropore volume using the density conversion factor 0.0015500 (for N2 at 77 K) that micromeritics uses. I think this is just the conversion factor from gas to liquid density. The results are much more reasonable than above (and they match MicroActive results).
I may have just totally missed something somewhere and be using the code wrong though!
Uncertainties in BET/Langmuir areas etc
Describe the bug
A clear and concise description of what the bug is.
To Reproduce
Steps to reproduce the behavior:
import pygaps as pg
from scipy import stats, optimize, interpolate
isotherm = pg.isotherm_from_json(r'carbon_x1_n2.json')
isotherm.plot()
result_dict = pg.area_BET(isotherm, verbose=True)
module 'scipy' has no attribute 'stats'
Expected behavior
A clear and concise description of what you expected to happen.
Screenshots
If applicable, add screenshots to help explain your problem.
System (please complete the following information):
Additional context
Add any other context about the problem here.
Please run the quick start one time to check the errors with the updated consols.
Where you are from?/How are you using pyGAPS? (optional)
I'm always curious to see how pyGAPS is used and if it is useful to people. Don't fill this in if you don't want to tell me.
I'm from the UK. It's for personal use. I'm an adsorption researcher.
just installed pygaps. import pgaps command gives:
0 selected samples
23 adsorbates
Hi, it is an excellent package and I tried to use it to do some mesopore psd recently.
And, while using des data, the psd result is accord with that from ASIQWin, however, while using ads data, the result retrieved from pyGAPS shows some discrepancy.
Take the example of MCM-41, the parameters are set as "pygaps-DH, ads , cylinder, Harkins/Jura, Kelvin...", and the psd result is pasted as below:
[ 0.00201822, 0.00225409, 0.00259436, 0.00289094, 0.00346204,
0.00396423, 0.0049243 , 0.00587327, 0.00698337, 0.00815182,
0.00949746, 0.01108028, 0.01263511, 0.01517044, 0.01848789,
0.0291542 , 0.07086728, 0.1233178 , 0.18424804, 0.60479161,
1.85880254, 0.55589063, -0.02554979, -0.09701637, -0.11233047,
-0.12062342, -0.12620523, -0.11850983, -0.1186818 , -0.12019221,
-0.10783947, -0.08993325, -0.06326952, -0.01934559, 0.06178637,
0.20850592, 0.49788791, 0.95116182, 1.4317689 , 0.43147767]
We can see many negative values in the above psd result, and it is weired.
After I checked the src code (pasted as follow), I found that a different Kelvin_radius equation was used while using ads data, because the meniscus geometry of ads and des is different.
def kelvin_radius()
if branch == 'ads':
if pore_geometry == 'cylinder':
m_geometry = 'cylindrical'
elif pore_geometry == 'sphere':
m_geometry = 'hemispherical'
elif pore_geometry == 'slit':
m_geometry = 'hemicylindrical'
...
elif branch == 'des':
if pore_geometry == 'cylinder':
m_geometry = 'hemispherical'
elif pore_geometry == 'sphere':
m_geometry = 'hemispherical'
elif pore_geometry == 'slit':
m_geometry = 'hemicylindrical'return - (2 * adsorbate_surface_tension * adsorbate_molar_density) / \ (geometry_factor * constants.gas_constant * temperature * numpy.log(pressure))
It is interesting, does anyone has any idea about that the ads/des have different "meniscus geometry"?
update: I figured it out. ASIQWin and others just treated ads as des.
Maybe the package can add a new option which allows us to treat ads as des while calculating psd.
The KH PSD method currently only has the slit pore type implemented. Expand for cylindrical and spherical type pores.
Hello, I am pretty new in this world of adsorption.
I am analyzing some data produced using a Micromeritics device (still need to figure out how to extract and parse the data), and I was quite unhappy about the results given by their software.
Your code looks much better than their proprietary software so I would like to use it to process my data.
I have been reading this book from J. Condon (https://www.sciencedirect.com/book/9780128187852/surface-area-and-porosity-determinations-by-physisorption), where he describes a theory that is more physically sounding for physisorption (and criticizes other models).
Have you considered implementing this model?
Do you have any advices about it?
Where you are from?/How are you using pyGAPS? (optional)
I'm a postdoc at UIUC, working on thermal protection materials for spacecraft.
PS. sorry for not following the template, but my question did not fit very well...
When running a plot of two or more isotherms with desorption branches
isos = [isotherm[0], isotherm[1]]
pygaps.plot_iso(isos, color=['b', 'k'] )
Outputs only blue coloured isotherms
It's important for certain calculations as in conversion between net, excess, and absolute (see #39) to know how the isotherm was measured.
I'd see this looking like;
measurement_mode
@pauliacomi I'm commenting this for when I get an MSc student on it.
Describe the bug
n-butane
is not recognised by CoolProp - it only recognises n-Butane
. Is it possible to change the capitalisation? Understand if this is more of an issue on the CoolProp end, and can submit an issue there if so.
To Reproduce
import pygaps.modelling as pgm
import pygaps.parsing as pgp
isotherm = pgp.isotherm_from_json('./docs/examples/data/isosteric/BAX 1500 - Isosteric Heat - 298.json')
model_isotherm = pgm.model_iso(isotherm, model='Langmuir')
p_triple = PropsSI('PTRIPLE', str(isotherm.adsorbate))
Throws error;
ValueError: Neither input to Props1SI [n-butane, PTRIPLE] is a valid fluid :: inputs were :"PTRIPLE","n-butane"
Adsorbate properties in database are in various units. Should transform all in SI and then do specific conversions where needed.
I try to load a isotherm from a .txt file, created from the Quantachrome software.
The Code is:
import pygaps.parsing as pgp
iso = pgp.isotherm_from_commercial('2022_01_27_CPG60_s3_77K_N2_CC_d2_M4_rescue (Isotherm).txt', "qnt", "txt")
but i get: ParsingError: Currently available manufacturers are ['bel', 'mic', '3p'])
The documentation says quantachrome is supported.
I'm confident i have the newest version installed.
System (please complete the following information):
Additional context
I installed Anaconda/Spyder fresh just to use the package
Some models have a form which cannot be solved analytically for the spreading pressure calculation:
Implement a numerical method to solve the integral.
I know this code mainly a framework for processing adsorption data and isotherm fitting but, it is sad that it contains only 1 example kernel for dft calculations, although it is very well written code.
Several improvements should be made to the fraction/percent modes.
First, both should be an alias of a single "fractional" mode with different conversion factors.
Then, must ensure that loading and material units are NONE in this mode.
Dear Dr Iacomi,
I am a PhD student working on gas separation, and recently I want to use your pygaps for some IAST calculations. When I tried to use pre-defined parameters for a model following (https://pygaps.readthedocs.io/en/master/examples/modelling.html,part [11]), I found that there is no output plot. After adding some print() to the code, I found out that it seems pgm.get_isotherm_model can not get params from params class. The following pictures are what I've changed and the result. You can see that even though I changed the "K" to 40 and 'pressure_range ' to (0.01,5), the resulted params is still 20 for K (0.01, 2) for pressure range(First line of the last picture, generated due to the print() in the sencond picture), and the parameters for the model are still nan(line 2-9 and line 11, generated due to the print() in the first picture). Could you help me check it and let me know what I should do to make it work?
p.s. I am using the spyder in anaconda to run the python(3.8). The system of my laptop is windows 10.
Many thanks,
Libin
Hi,
Is your feature request related to a problem? Please describe.
I am working on incorporating pyGAPS into a local tool to be used in my research group for gas adsorption measurements. However I stumbled upon a source of unclearity with pyGAPS (at least to me. See https://pygaps.readthedocs.io/en/master/manual/units.html?highlight=STP). I assumed the PointIsotherm used STP as loading_unit but this it not the case. So I need convert our adsorption data to real conditions before using pyGAPS. This of course means that the plotted isotherms have different y-axis scaling. We have a lot of old isotherm data that we often reference. For us, it is therefore preferred to have the option of using "cm3(STP)".
Describe the solution you'd like
Add STP alternative to available loading units.
Otherwise I really like pyGAPS and find it very usefull.
P.S I understand that there are different opinions on whether to use STP or real conditions.
Hi, I am a graduate student and my study is about liquid phase adsorption (fuel oil). Your lib is amazing, but I find it seems a pity that the liquid phase adsorption isotherm is unsupported in this lib. I took a look at source code and I found your baseisotherm.py
only designed for gas phase but not liquid phase. Maybe some changes is important. Just like adding a parameter phase in baseisotherm.py and inserting a if elif
in __init__
when program reads properties. Andconvert_unit.py
needs some changes. The reason I submit this issue is I am afraid you have other method to solve this problem before I modify the baseisotherm.py
by my ideas.
recorded high pressure isotherms are usually excess isotherms
allow for conversion to absolute and back
Describe the bug
Done in jupyter lab: using import_from_aif from a converted to .aif format (from quantachrome via the online web-aif converter) - fails on import due to string parsing?
To Reproduce
import pygaps.parsing as pgp
isotherm = pgp.isotherm_from_aif('MySample1.aif')
Errors reported:
FutureWarning: errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead
).apply(pandas.to_numeric, errors='ignore')
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
Cell In[4], line 3
1 import pygaps.parsing as pgp
----> 3 isotherm = pgp.isotherm_from_aif('MySample1.aif')
File [~\AppData\Local\anaconda3\Lib\site-packages\pygaps\parsing\aif.py:350](http://localhost:8888/lab/tree/Desktop/~/AppData/Local/anaconda3/Lib/site-packages/pygaps/parsing/aif.py#line=349), in isotherm_from_aif(str_or_path, **isotherm_parameters)
348 # loading/material units
349 loading_material_units = block.find_value('_units_loading').strip("'")
--> 350 loading_material_dict = parse_loading_string(loading_material_units)
351 raw_dict.update(loading_material_dict)
353 # temperature units
File [~\AppData\Local\anaconda3\Lib\site-packages\adsorption_file_parser\utils\unit_parsing.py:199](http://localhost:8888/lab/tree/Desktop/~/AppData/Local/anaconda3/Lib/site-packages/adsorption_file_parser/utils/unit_parsing.py#line=198), in parse_loading_string(loading_string, missing_units)
197 if len(unit_components) != 2:
198 unit_components = loading_string_clean.split(' ')
--> 199 unit_components[1] = unit_components[1].replace('-1', '')
200 if len(unit_components) != 2:
201 raise ParsingError(error_text)
IndexError: list index out of range
Expected behavior
pygaps importer would have imported loading units appropriately... from the .aif the header info is:
data_raw2aif _exptl_operator 'User1' _exptl_date 2024-08-23T00:00:00 _exptl_instrument 'Autosorb iQ Station 2' _exptl_adsorptive 'Nitrogen' _exptl_temperature 77.35 _adsnt_sample_mass 0.0717 _adsnt_sample_id 'sample1' _adsnt_material_id 'unknown' _units_temperature 'K' _units_pressure 'Torr' _units_mass 'g' _units_loading 'cc' _audit_aif_version d546195
System (please complete the following information):
Additional context
Had issues manually creating point-isotherms from data, but further work I think will make it work... coming at this from a few different directions... trying to compare multiple machines (Nova, Quantachrome, Micromeritics)
Where you are from?/How are you using pyGAPS? (optional)
Researcher at Univ. Of IL working on graphene-like carbons
Describe the bug
A clear and concise description of what the bug is.
CalculationError: Root finding for adsorbed phase mole fractions failed.
This is likely because the default guess is not good enough.
Try a different starting guess for the adsorbed phase mole fractions by
passing an array adsorbed_mole_fraction_guess to this function. Scipy error
message: Number of calls to function has reached maxfev = 400.
TypeError: unsupported operand type(s) for -: 'str' and 'str'
To Reproduce
Steps to reproduce the behavior:
System (please complete the following information):
Additional context
Add any other context about the problem here.
I want to know what PRESSURE in this picture means PRESSURE
https://pygaps.readthedocs.io/en/master/examples/iast.html
IN[4]'s Picture
When I attempt to install a new version (after my edits) of pyGAPS,
pip install ~/pyGAPS/
the modules in parsing .csv
and .json
throw an error such as;
AttributeError: partially initialized module 'json' has no attribute 'loads' (most likely due to a circular import)
or
ImportError: cannot import name 'QUOTE_NONNUMERIC' from partially initialized module 'csv' (most likely due to a circular import) (/home/pcxtsbl/pyGAPS/src/pygaps/parsing/csv.py)
I think this is because they have the same external modules .
Changing names to, for example .csv_
fixes this issue.
For the IAST examples section, the following was given as an example, but the "%run import.ipynb" does not work and is there any reference as to which file was selected to give the information following the 3 imports? Thanks.
%run import.ipynb
import matplotlib.pyplot as plt
import numpy
Selected 5 isotherms with nitrogen at 77K
Selected 2 room temperature calorimetry isotherms
Selected 2 isotherms for IAST calculation
Selected 3 isotherms for isosteric enthalpy calculation
When using the operations that return data from multiple tables, such as db_get_experiment
, the operation takes a lot of time.
Likely an issue with optimizing sqlite connections.
In the list of models, all are given in lower case. From my implementation, they don't actually work like that Not all models provided correspond to internal models
It does work if you use the appropriate case though i.e. 'Langmuir', 'DSLangmuir', 'GAB'
.
Documentation could be updated to reflect this.
Thanks,
Scott
Would it be possible to make fitted isotherm parameters(e.g. for the Langmuir isotherm, 'n_m' and 'K' ) exposed directly to users. I know that I could print those parameters out by the "print_info" function and examine them one at a time, but for me I need to analyze many isotherms at once, It would make my job much easier if I could somehow access those fitted isotherm parameters directly from the <class: ModelIsotherm>.
Describe the bug
On converting isotherm pressures from, e.g. bar to Pa modelling of the resultant isotherm is poor when using DSLangmuir
or TSLangmuir
.
To Reproduce
The following code shows the creation of a point isotherm (pressure in bar), then modelling with Langmuir
, DSLangmuir
and TSLangmuir
. The isotherm pressures are then converted to Pa, and the three models are repeated.
import matplotlib.pyplot as plt
import pygaps.graphing as pgg
from pygaps.utilities.exceptions import CalculationError
pressure = [0.00131, 0.0117, 0.0273, 0.0553, 0.0803, 0.1065, 0.1341, 0.1652, 0.2048, 0.2487, 0.2984, 0.3485, 0.3994, 0.5023, 0.6516, 0.7493, 0.849, 0.9514, 1.1024, 1.1986, 1.2982, 1.3991, 1.4989, 1.5991, 2.0004, 2.9996, 3.9994, 4.9987, 5.9966, 6.9959, 7.9954, 8.9957, 9.9948, 10.9939, 11.9932, 12.9931, 13.9894, 14.9918, 15.9893, 16.9893, 17.4875, 17.988, 18.4864, 18.9869, 19.9846]
loading = [0.4768, 0.5884, 0.6847, 0.838, 0.9749, 1.0966, 1.2325, 1.3781, 1.553, 1.7379, 1.9402, 2.1289, 2.3172, 2.6662, 3.1311, 3.415, 3.6924, 3.9625, 4.3376, 4.5707, 4.8026, 5.0296, 5.2456, 5.4578, 6.2168, 7.7802, 9.0279, 10.0537, 10.9245, 11.6784, 12.3361, 12.9208, 13.4434, 13.9251, 14.3579, 14.7502, 15.1165, 15.4457, 15.7571, 16.0334, 16.1714, 16.3015, 16.4303, 16.5481, 16.7758]
isotherm = pg.PointIsotherm(
pressure=pressure,
loading=loading,
material='example',
adsorbate='co2',
temperature=298,
temperature_unit='K',
pressure_mode='absolute',
pressure_unit='bar',
loading_basis='molar',
loading_unit='mmol',
material_basis='mass',
material_unit='g',
)
for unit in ['bar', 'Pa']:
isotherm.convert( # only thing actually converted is pressure
pressure_mode='absolute',
pressure_unit=unit,
loading_basis='molar',
loading_unit='mmol',
material_basis='mass',
material_unit='g',
)
for m in ['Langmuir', 'DSLangmuir', 'TSLangmuir']:
print(
f'isotherm pressures converted to {unit} '
f'and modelling with {m}.'
)
try:
model = pg.ModelIsotherm.from_pointisotherm(
isotherm, branch='ads',
model=m,
)
print(model.model.params)
pgg.plot_iso(
[model, isotherm]
)
plt.show()
except CalculationError as e:
print(e)
continue
Model fit is good for all three models before conversion to Pa. It is bad after conversion for fitting to DSLangmuir
and TSLangmuir
. Strangely, fitting to Langmuir
doesn't have this behaviour.
Expected behavior
Should be same fit regardless of units!
Screenshots
Modelling in bar:
System (please complete the following information):
Models should also be able to be converted from different units by converting individual model parameters.
Each model to have an individual implementation of the convert function that is adapted.
Describe the bug
AIF file versions (from the webapp) are lately being given as a alphanumeric string;
_audit_aif_version 6acf6ef
This doesn't play nice with the AIF parser (isotherm_from_aif()
in pygaps.parsing.aif
), as it tries to force the the verion number, in this case 6acf6ef
to be a float.
version = block.find_value('_audit_aif_version').strip("'")
if not version or float(version.strip("'")) < float(_parser_version):
I don't know if this is meant to be hexadecimal or something, but the parser doesn't know what to do with it.
In my local version I've just deleted the version check!
When isotherm.loading_at()
is called, sometimes a ValueError
occurs at the limits of the isotherm. This appears to be due to conversion of the pressures to float16, which are not quite the same value as the original pressure data.
Variable file refers to attached aif file (annoyingly, in a .zip)
import pygaps.parsing as pgp
isotherm = pgp.isotherm_from_aif(file)
pressure = isotherm.pressure(branch='ads', pressure_mode='relative')
loading = isotherm.loading_at(
pressure=self.pressure,
branch='ads',
pressure_mode='relative',
loading_unit='mol',
loading_basis='molar',
material_unit='g',
material_basis='mass',
)
This leads to the following Traceback;
loading = self.l_interpolator(pressure)
File "/home/pcxtsbl/anaconda3/lib/python3.9/site-packages/pygaps/utilities/isotherm_interpolator.py", line 70, in __call__
return self.interp_fun(data)
File "/home/pcxtsbl/.local/lib/python3.9/site-packages/scipy/interpolate/_polyint.py", line 80, in __call__
y = self._evaluate(x)
File "/home/pcxtsbl/.local/lib/python3.9/site-packages/scipy/interpolate/_interpolate.py", line 752, in _evaluate
below_bounds, above_bounds = self._check_bounds(x_new)
File "/home/pcxtsbl/.local/lib/python3.9/site-packages/scipy/interpolate/_interpolate.py", line 786, in _check_bounds
raise ValueError("A value ({}) in x_new is above "
ValueError: A value (762.5309448200001) in x_new is above the interpolation range's maximum value (762.53094482).
As you can see, the only reason x_new
is out of range is the addition of 5 bits to the original pressure value.
Weirdly, this does not occur if we cut off the limits of the pressure list.
For example;
pressure = isotherm.pressure()[1:-1]
loading = isotherm.loading_at(
pressure=self.pressure,
branch='ads',
pressure_mode='relative',
loading_unit='mol',
loading_basis='molar',
material_unit='g',
material_basis='mass',
)
Returns no error.
Maybe the IsothermInterpolater()
class should have its __init__()
set up like this?
def __init__(
self,
known_data,
interp_data,
interp_branch='ads',
interp_kind='linear',
interp_fill=True,
):
But perhaps you have a better way.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.