├── betsi ├── __init__.py ├── .idea │ ├── .gitignore │ ├── misc.xml │ ├── vcs.xml │ ├── inspectionProfiles │ │ └── profiles_settings.xml │ ├── modules.xml │ ├── betsiv8.iml │ ├── markdown-navigator.xml │ └── markdown-navigator-enh.xml ├── __pycache__ │ ├── lib.cpython-37.pyc │ ├── utils.cpython-37.pyc │ └── plotting.cpython-37.pyc ├── __main__.py ├── data │ ├── MCM-41.csv │ ├── PCN-777.csv │ ├── NU-1000.csv │ ├── TPB-DMTP-COF.csv │ ├── NU-1102.csv │ ├── ZIF-8.csv │ ├── Mg-MOF-74.csv │ ├── Al fumarate.csv │ ├── Zeolite-13X.csv │ ├── MIL-101.csv │ ├── UiO-66-NH2.csv │ ├── EXP_ZIF-8powder.csv │ ├── HKUST-1.csv │ ├── DMOF-1.csv │ ├── MOF-5.csv │ ├── NU-1105.csv │ ├── MIL-100.csv │ ├── NU-1104.csv │ └── UiO-66.csv ├── utils.py ├── lib.py ├── plotting.py └── gui.py ├── MANIFEST.in ├── .idea ├── .gitignore ├── misc.xml ├── vcs.xml ├── inspectionProfiles │ └── profiles_settings.xml ├── betsi-gui.iml ├── modules.xml ├── betsi.iml ├── markdown-navigator.xml └── markdown-navigator-enh.xml ├── BETSI-v2.0.pdf ├── cli.py ├── docs └── images │ ├── step-1.png │ ├── step-2.png │ ├── step-3.png │ ├── step-4.png │ ├── step-5.png │ ├── step-6.png │ ├── step-7.png │ ├── step-8.png │ ├── step-9.png │ ├── a2ml_logo.png │ ├── step-10.png │ ├── step-11.png │ ├── step-12.png │ ├── step-13.png │ ├── step-14.png │ ├── BETSI-logo.jpeg │ ├── betsi_logo.PNG │ └── executables-banner.PNG ├── requirements.txt ├── executables ├── BETSI_v2_0_linux.zip ├── BETSI_v2_0_mac.zip ├── BETSI_v2_0_windows.exe ├── BETSI_v2_0_linux-faster_startup.zip ├── BETSI_v2_0_mac_faster_startup.zip └── BETSI_v2_0_windows_faster_startup.zip ├── .travis.yml ├── .gitattributes ├── LICENSE.txt ├── setup.py ├── cli.spec └── README.md /betsi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft betsi -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Default ignored files 3 | /workspace.xml -------------------------------------------------------------------------------- /betsi/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /BETSI-v2.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/BETSI-v2.0.pdf -------------------------------------------------------------------------------- /cli.py: -------------------------------------------------------------------------------- 1 | from betsi.gui import runbetsi 2 | 3 | runbetsi() # pylint: disable=no-value-for-parameter -------------------------------------------------------------------------------- /docs/images/step-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/step-1.png -------------------------------------------------------------------------------- /docs/images/step-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/step-2.png -------------------------------------------------------------------------------- /docs/images/step-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/step-3.png -------------------------------------------------------------------------------- /docs/images/step-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/step-4.png -------------------------------------------------------------------------------- /docs/images/step-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/step-5.png -------------------------------------------------------------------------------- /docs/images/step-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/step-6.png -------------------------------------------------------------------------------- /docs/images/step-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/step-7.png -------------------------------------------------------------------------------- /docs/images/step-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/step-8.png -------------------------------------------------------------------------------- /docs/images/step-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/step-9.png -------------------------------------------------------------------------------- /docs/images/a2ml_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/a2ml_logo.png -------------------------------------------------------------------------------- /docs/images/step-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/step-10.png -------------------------------------------------------------------------------- /docs/images/step-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/step-11.png -------------------------------------------------------------------------------- /docs/images/step-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/step-12.png -------------------------------------------------------------------------------- /docs/images/step-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/step-13.png -------------------------------------------------------------------------------- /docs/images/step-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/step-14.png -------------------------------------------------------------------------------- /docs/images/BETSI-logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/BETSI-logo.jpeg -------------------------------------------------------------------------------- /docs/images/betsi_logo.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/betsi_logo.PNG -------------------------------------------------------------------------------- /docs/images/executables-banner.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/docs/images/executables-banner.PNG -------------------------------------------------------------------------------- /betsi/__pycache__/lib.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/betsi/__pycache__/lib.cpython-37.pyc -------------------------------------------------------------------------------- /betsi/__pycache__/utils.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/betsi/__pycache__/utils.cpython-37.pyc -------------------------------------------------------------------------------- /betsi/__pycache__/plotting.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fairen-group/betsi-gui/HEAD/betsi/__pycache__/plotting.cpython-37.pyc -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.19.3 2 | scipy==1.5.4 3 | matplotlib==3.2.2 4 | PyQt5==5.9.2 5 | pandas==1.1.5 6 | seaborn==0.11.0 7 | statsmodels==0.12.1 8 | xlrd==2.0.1 9 | -------------------------------------------------------------------------------- /executables/BETSI_v2_0_linux.zip: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9fe6a2109a0c40e5fef4be93cf4fefb0f510a77fa996e09f3f8470581c0553d4 3 | size 117570162 4 | -------------------------------------------------------------------------------- /executables/BETSI_v2_0_mac.zip: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:2eff4fd62f6050c8bb5b9744d2cb9cb68f2d9c479337dc6c2b6dec047f2fde67 3 | size 117573850 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | install: 5 | - easy_install distribute 6 | - pip install -r requirements.txt 7 | script: cd betsi; python3 gui.py 8 | -------------------------------------------------------------------------------- /executables/BETSI_v2_0_windows.exe: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d0d973d7b1461fe02cfd1e82bafa5d7e634076dfd09dcb86378551e48cbb5a47 3 | size 183182879 4 | -------------------------------------------------------------------------------- /betsi/__main__.py: -------------------------------------------------------------------------------- 1 | from betsi.gui import runbetsi 2 | 3 | def main(): 4 | runbetsi() # pylint: disable=no-value-for-parameter 5 | 6 | if __name__ == "__main__": 7 | main() -------------------------------------------------------------------------------- /executables/BETSI_v2_0_linux-faster_startup.zip: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:8dc2a7c7dc1478c65480a4a6485c8bf4e79c8bc53195d981337b0bdc47d39203 3 | size 119517011 4 | -------------------------------------------------------------------------------- /executables/BETSI_v2_0_mac_faster_startup.zip: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a134968d4c6d701f1e71996807c0a0490a99e134b9016ef1048dc937e3bf68dc 3 | size 239720524 4 | -------------------------------------------------------------------------------- /executables/BETSI_v2_0_windows_faster_startup.zip: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7845386f43b94ac94d55ff6daf8280db4d83386880f15507c64c5cc970603899 3 | size 171295906 4 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /betsi/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /betsi/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /betsi/.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/betsi-gui.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /betsi/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | executables/betsi-linux filter=lfs diff=lfs merge=lfs -text 2 | executables/betsi-windows.exe filter=lfs diff=lfs merge=lfs -text 3 | executables/**/* filter=lfs diff=lfs merge=lfs -text 4 | executables/* filter=lfs diff=lfs merge=lfs -text 5 | executables/*git filter=lfs diff=lfs merge=lfs -text 6 | lfs filter=lfs diff=lfs merge=lfs -text 7 | ls-files filter=lfs diff=lfs merge=lfs -text 8 | -------------------------------------------------------------------------------- /betsi/.idea/betsiv8.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /.idea/betsi.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /betsi/data/MCM-41.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 9.79E-03,141.8502983 3 | 1.30E-02,147.3 4 | 0.016992215,153.9 5 | 0.021993683,161.7 6 | 0.02998674,172.9 7 | 0.039658038,183.7882938 8 | 0.056022606,197.1 9 | 0.071295439,206.8902405 10 | 0.104927504,225.1116935 11 | 0.129699104,236.6158331 12 | 0.159960572,249.4753063 13 | 0.191745691,262.3267321 14 | 0.222604245,274.802244 15 | 0.252936316,287.7227679 16 | 0.284027412,303.5133822 17 | 0.305135298,320.3 18 | 0.316012885,333.9432213 19 | 0.333871196,392.7 20 | 0.343960535,420.7730051 21 | 0.372991369,458.9 22 | 0.42755174,493.6786212 23 | 0.447576408,495.090764 24 | 0.476895088,496.9751534 25 | 0.499735922,498.29126 26 | 0.529857594,499.8883749 27 | 0.560582034,501.3842062 28 | 0.591212993,502.7760393 29 | 0.621878896,504.132203 30 | 0.652784998,505.4119977 31 | 0.683268469,506.6427636 32 | 0.713947513,507.8582389 33 | 0.744563968,509.0861455 34 | 0.775127231,510.350724 35 | 0.806787691,511.6682351 36 | 0.836549791,512.9731992 37 | 0.867018855,514.4407391 38 | 0.897225551,516.3071265 39 | 0.937868963,520.8782567 -------------------------------------------------------------------------------- /betsi/data/PCN-777.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 0.002879,120.229 3 | 0.005757,173.643 4 | 0.010075,221.693 5 | 0.018711,261.669 6 | 0.023028,280.32 7 | 0.035982,306.875 8 | 0.047496,322.759 9 | 0.066207,346.566 10 | 0.087796,370.334 11 | 0.094992,380.93 12 | 0.109385,394.103 13 | 0.126656,415.256 14 | 0.149684,436.333 15 | 0.168395,454.795 16 | 0.188546,473.237 17 | 0.208695,494.352 18 | 0.228845,515.467 19 | 0.246116,531.275 20 | 0.269145,555.025 21 | 0.289294,578.813 22 | 0.303687,610.694 23 | 0.310883,618.617 24 | 0.318079,629.213 25 | 0.322398,637.174 26 | 0.329594,645.096 27 | 0.331034,650.423 28 | 0.33679,661.037 29 | 0.339669,666.344 30 | 0.345426,679.632 31 | 0.348305,692.957 32 | 0.355501,826.492 33 | 0.356941,834.491 34 | 0.358379,928.013 35 | 0.388605,1341.87 36 | 0.394362,1352.48 37 | 0.401558,1363.08 38 | 0.402997,1395.13 39 | 0.418829,1400.27 40 | 0.451933,1405.18 41 | 0.490793,1410.01 42 | 0.51814,1414.99 43 | 0.567074,1419.69 44 | 0.611692,1421.78 45 | 0.633281,1426.84 46 | 0.726835,1428.28 47 | 0.752741,1433.28 48 | 0.77577,1438.32 49 | 0.823266,1440.37 50 | 0.860687,1442.55 51 | 0.899547,1447.38 -------------------------------------------------------------------------------- /betsi/data/NU-1000.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 0.003831,245.411 3 | 0.004887863,260.7 4 | 0.005746,280.193 5 | 0.006480225,310.4 6 | 0.006637632,318.6 7 | 0.007204856,344.6 8 | 0.007662,355.556 9 | 0.009205078,364.1 10 | 0.011492,371.014 11 | 0.013393309,377.9 12 | 0.018536751,394.8 13 | 0.02107,401.932 14 | 0.028731,419.324 15 | 0.049801,448.309 16 | 0.066803487,463 17 | 0.080447,473.43 18 | 0.099601,488.889 19 | 0.109178,494.686 20 | 0.120671,504.348 21 | 0.133720965,515.5 22 | 0.147487,527.536 23 | 0.160894,539.13 24 | 0.168556,546.86 25 | 0.178132,556.522 26 | 0.18771,566.184 27 | 0.197287,583.575 28 | 0.199488238,600.2 29 | 0.201118,614.493 30 | 0.204948,643.478 31 | 0.206864,660.87 32 | 0.208461689,678.2 33 | 0.210695,693.72 34 | 0.224103,699.517 35 | 0.268157,707.246 36 | 0.298803,709.179 37 | 0.33328,713.043 38 | 0.358181,716.908 39 | 0.402235,720.773 40 | 0.45012,724.638 41 | 0.503751,726.57 42 | 0.530567,728.502 43 | 0.555468,730.435 44 | 0.603353,730.435 45 | 0.655069,734.3 46 | 0.704869,736.232 47 | 0.735516,738.164 48 | 0.760417,738.164 49 | 0.806386,742.029 50 | 0.856187,742.029 51 | 0.904072,745.894 52 | 0.940464,747.826 53 | 0.999842,757.488 -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020, Adsorption and Advanced Materials Lab (AAML), Department of 4 | Chemical Engineering & Biotechnology, University of Cambridge. All rights reserved. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | from setuptools import setup, find_packages 3 | 4 | # The directory containing this file 5 | HERE = pathlib.Path(__file__).parent 6 | 7 | # The text of the README file 8 | README = (HERE / "README.md").read_text() 9 | 10 | with open("requirements.txt", "r") as fh: 11 | REQUIREMENTS = [line.strip().split(";")[0] for line in fh] 12 | 13 | # This call to setup() does all the work 14 | setup( 15 | name="betsi-gui", 16 | version="1.0.20", 17 | description="BET Surface Identification - a program that fully implements the rouquerol criteria", 18 | long_description=README, 19 | long_description_content_type="text/markdown", 20 | url="https://github.com/fairen-group/betsi-gui", 21 | author="James Rampersad, Johannes W.M. Osterrieth & Nakul Rampal", 22 | author_email="nr472@cam.ac.uk", 23 | license="MIT", 24 | classifiers=[ 25 | "License :: OSI Approved :: MIT License", 26 | "Programming Language :: Python :: 3", 27 | "Programming Language :: Python :: 3.7", 28 | ], 29 | python_requires=">=3.7.3", 30 | packages=find_packages(), 31 | include_package_data=True, 32 | install_requires= REQUIREMENTS, 33 | entry_points={ 34 | "console_scripts": [ 35 | "betsi=betsi.__main__:main", 36 | ] 37 | }, 38 | ) -------------------------------------------------------------------------------- /betsi/data/TPB-DMTP-COF.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 0.001665872,196.9815483 3 | 0.00194442,204.5961096 4 | 0.002868962,225.7845273 5 | 0.003965587,245.5310835 6 | 0.004867865,259.2492878 7 | 0.005726495,270.593821 8 | 0.006790925,282.9579083 9 | 0.007894831,294.2735885 10 | 0.008900371,303.4024294 11 | 0.009887064,311.5334684 12 | 0.011998184,325.7 13 | 0.013995772,337.7 14 | 0.019197382,364.2804376 15 | 0.023006005,380.5 16 | 0.028849446,402.0046323 17 | 0.038241534,432.0550174 18 | 0.0500177,465.0455606 19 | 0.057059164,483.0427724 20 | 0.069001998,511.3181513 21 | 0.07905353,533.2231994 22 | 0.088927524,553.5114555 23 | 0.098902977,572.6886873 24 | 0.117842895,606.2501179 25 | 0.137931437,638.7028528 26 | 0.158643117,670.0773885 27 | 0.178416291,700.0028433 28 | 0.197220756,730.8059603 29 | 0.214357902,773.9836293 30 | 0.224922361,892.8 31 | 0.235340888,999.3264478 32 | 0.268274737,1042.279024 33 | 0.295846618,1060.152312 34 | 0.326118904,1078.688371 35 | 0.342338021,1087.506136 36 | 0.360029297,1096.14 37 | 0.380357356,1105.531046 38 | 0.399925353,1114.12165 39 | 0.448298456,1134.842174 40 | 0.500040283,1156.094989 41 | 0.549395048,1176.262333 42 | 0.599734426,1197.327239 43 | 0.64932966,1219.068473 44 | 0.699352169,1242.598927 45 | 0.74962873,1269.762338 46 | 0.798779288,1301.439784 47 | 0.847691866,1344.863378 48 | 0.896305484,1413.54769 49 | 0.946154066,1564.014228 50 | 0.998120009,2313.935013 -------------------------------------------------------------------------------- /betsi/data/NU-1102.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 0.009596,19.41 3 | 0.009612,38.89 4 | 0.009916,88.27 5 | 0.010389,141.52 6 | 0.011031505,234 7 | 0.011550807,318.5 8 | 0.012094554,390.4 9 | 0.012523,419.91 10 | 0.014087,450.63 11 | 0.015418,477.63 12 | 0.017685,512.55 13 | 0.018549,556.89 14 | 0.019567,592.81 15 | 0.020351,627.73 16 | 0.020587,649.53 17 | 0.022229,673.56 18 | 0.024027,691.89 19 | 0.025434,706.26 20 | 0.027544,734.5 21 | 0.029967,756.8 22 | 0.032625,778.85 23 | 0.035154,802.93 24 | 0.037262,822.2 25 | 0.043028,842.26 26 | 0.048236,867.44 27 | 0.054746,888.88 28 | 0.059644,908.95 29 | 0.062374,936.48 30 | 0.067892,962.64 31 | 0.074278,993.91 32 | 0.080293,1024.59 33 | 0.084014,1047.01 34 | 0.090585,1063.54 35 | 0.09759,1088.92 36 | 0.105525,1106.24 37 | 0.115814,1131.24 38 | 0.134969,1165.69 39 | 0.15282,1192.87 40 | 0.161188,1200.36 41 | 0.207176,1222.5 42 | 0.226389,1227.66 43 | 0.247586,1231.85 44 | 0.256944,1234.82 45 | 0.269588,1237.61 46 | 0.291032,1243.17 47 | 0.30975,1244.6 48 | 0.33423,1248.6 49 | 0.359331,1252.79 50 | 0.383316,1255.42 51 | 0.40854,1258.83 52 | 0.434137,1261.25 53 | 0.460167,1263.68 54 | 0.484834,1264.14 55 | 0.510306,1267.36 56 | 0.535346,1271.55 57 | 0.58598,1274.44 58 | 0.611212,1278.79 59 | 0.661582,1281.73 60 | 0.686571,1282.43 61 | 0.711659,1284.99 62 | 0.762915,1285.44 63 | 0.788102,1287.38 64 | 0.813091,1288.08 65 | 0.838867,1289.71 66 | 0.889237,1290.47 67 | 0.894846,1291.73 68 | 0.911964,1293.02 69 | 0.944823,1295.3 -------------------------------------------------------------------------------- /betsi/data/ZIF-8.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 4.20E-05,20.349 3 | 9.97E-05,40.698 4 | 0.000148121,60.914 5 | 0.000188506,81.263 6 | 0.000224278,101.612 7 | 0.000259844,122.094 8 | 0.000299953,142.31 9 | 0.000349057,162.26 10 | 0.000417927,183.54 11 | 0.000521085,203.49 12 | 0.000684773,223.44 13 | 0.000949862,243.39 14 | 0.001422718,263.34 15 | 0.00193056,275.31 16 | 0.002936687,288.61 17 | 0.003871099,295.26 18 | 0.004945074,301.91 19 | 0.005730774,305.9 20 | 0.006698907,312.55 21 | 0.008054492,323.19 22 | 0.008892109,328.51 23 | 0.009781263,332.5 24 | 0.02074716,356.44 25 | 0.029110932,364.42 26 | 0.038163686,400.33 27 | 0.04836001,413.63 28 | 0.063774299,420.28 29 | 0.069776865,421.61 30 | 0.08004303,424.27 31 | 0.090083618,425.6 32 | 0.10043044,428.26 33 | 0.145027143,433.58 34 | 0.202710154,438.9 35 | 0.254860289,442.89 36 | 0.305344376,446.88 37 | 0.354387044,449.54 38 | 0.399269895,452.2 39 | 0.449413315,454.86 40 | 0.499421734,457.52 41 | 0.549130569,460.18 42 | 0.599553456,462.84 43 | 0.648965909,465.5 44 | 0.699055532,468.16 45 | 0.748964062,470.82 46 | 0.7992439,474.81 47 | 0.809235821,476.14 48 | 0.819317209,476.14 49 | 0.829266798,477.47 50 | 0.839403863,478.8 51 | 0.850174553,480.13 52 | 0.859575733,481.46 53 | 0.869079932,482.79 54 | 0.879065428,484.12 55 | 0.889536882,485.45 56 | 0.899627608,488.11 57 | 0.90986306,489.44 58 | 0.919854649,492.1 59 | 0.929147394,494.76 60 | 0.939055955,498.75 61 | 0.948884511,502.74 62 | 0.959088154,509.39 63 | 0.969051619,517.37 64 | 0.978076108,529.34 65 | 0.986984423,549.29 66 | 0.994455819,585.2 -------------------------------------------------------------------------------- /betsi/data/Mg-MOF-74.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 0.0000341,2.679731275 3 | 0.0000585,3.337321679 4 | 0.0000829,9.384074149 5 | 0.000106975,29.27503048 6 | 0.000125602,49.16683188 7 | 0.000144069,69.05532465 8 | 0.000163778,88.93400688 9 | 0.000187348,108.8053837 10 | 0.000208741,117.6434662 11 | 0.00026291,123.3817269 12 | 0.00032538,143.2548701 13 | 0.000393299,163.0549543 14 | 0.00040154,166.1491326 15 | 0.000460131,177.7 16 | 0.000541423,185.8657739 17 | 0.000650309,188.4257665 18 | 0.000858217,194.4493198 19 | 0.001130007,201.0504363 20 | 0.001352074,204.5451253 21 | 0.001629688,207.8585873 22 | 0.002048444,211.8505979 23 | 0.002672633,216.3557661 24 | 0.00340465,219.8561064 25 | 0.004236093,222.3693104 26 | 0.005229068,224.4735318 27 | 0.006804549,227.0193331 28 | 0.008670638,229.0923041 29 | 0.010602531,230.66 30 | 0.013560594,232.4924596 31 | 0.017513597,234.2534634 32 | 0.02198175,235.6939878 33 | 0.027797594,237.1443946 34 | 0.035558333,238.6413654 35 | 0.045087695,240.0641898 36 | 0.057084319,241.4817518 37 | 0.073852847,242.98 38 | 0.093504522,244.4183565 39 | 0.117917986,245.8493671 40 | 0.148441409,247.3471616 41 | 0.187453493,248.9415548 42 | 0.236737861,250.6734233 43 | 0.30102165,252.5762015 44 | 0.376151248,254.5711403 45 | 0.475697694,256.9706346 46 | 0.539978839,258.3099839 47 | 0.603025345,259.5759029 48 | 0.644358128,260.2908649 49 | 0.684218752,260.9270644 50 | 0.72439712,261.5376325 51 | 0.764352288,262.2155243 52 | 0.790607354,262.6794448 53 | 0.81598638,263.1544686 54 | 0.841261992,263.7248281 55 | 0.866822335,264.3598643 56 | 0.914684081,266 57 | 0.942892019,267.3403463 58 | 0.967733315,269.1591428 -------------------------------------------------------------------------------- /betsi/data/Al fumarate.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 6.51E-05,0.17817321 3 | 9.45E-05,0.349631375 4 | 0.000140029,0.59994436 5 | 0.000168502,1.155145865 6 | 0.000212523,3.039106608 7 | 0.000256869,22.95957802 8 | 0.000285953,42.88725781 9 | 0.000318927,62.80599158 10 | 0.00032856,63.58629756 11 | 0.000397185,67.50137289 12 | 0.000427058,87.43908993 13 | 0.000458442,107.3250352 14 | 0.000511064,127.1861418 15 | 0.000580619,145.2756391 16 | 0.000637337,156.142065 17 | 0.000890495,175.8379637 18 | 0.001019138,182.5277364 19 | 0.001355172,190.5388013 20 | 0.001632038,195.2730373 21 | 0.002111946,200.3510141 22 | 0.002599008,203.990202 23 | 0.003298337,208.0650155 24 | 0.004368838,212.1661725 25 | 0.005230467,214.6878989 26 | 0.006777789,218.0580083 27 | 0.008474152,220.9052842 28 | 0.010761704,223.9901988 29 | 0.014077117,227.1368389 30 | 0.017025411,229.3487793 31 | 0.022138234,232.3009849 32 | 0.027920961,234.8889913 33 | 0.035532871,237.4528331 34 | 0.044729196,239.8483793 35 | 0.056862545,242.3131476 36 | 0.074640451,244.9876403 37 | 0.094446941,247.2833845 38 | 0.119931747,249.5926078 39 | 0.149860444,251.759821 40 | 0.188606945,254.05 41 | 0.237609088,256.4403221 42 | 0.30241524,259.2404553 43 | 0.375744873,262.2958431 44 | 0.474393139,266.7574807 45 | 0.538919084,270.3468677 46 | 0.602620402,274.6711857 47 | 0.64403838,278.1431281 48 | 0.684098233,282.0798043 49 | 0.723912436,286.6403969 50 | 0.763564176,292.0588002 51 | 0.790607423,296.3022283 52 | 0.815628686,300.7223269 53 | 0.840576799,305.7748296 54 | 0.865722821,311.7378262 55 | 0.91707011,330.6836475 56 | 0.940410038,346.4244636 57 | 0.961980007,368.4154227 58 | 0.97070987,381.5347285 -------------------------------------------------------------------------------- /cli.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | block_cipher = None 4 | 5 | 6 | a = Analysis(['cli.py'], 7 | pathex=['D:\\betsi-windows'], 8 | binaries=[], 9 | datas=[], 10 | hiddenimports=[], 11 | hookspath=[], 12 | runtime_hooks=[], 13 | excludes=['statsmodels'], 14 | win_no_prefer_redirects=False, 15 | win_private_assemblies=False, 16 | cipher=block_cipher, 17 | noarchive=False) 18 | a.datas += Tree("D:\\anaconda\\Lib\\site-packages\\statsmodels", prefix="statsmodels") 19 | a.datas += Tree("D:\\anaconda\Lib\\site-packages\\numpy", prefix="numpy") 20 | a.datas += Tree("D:\\anaconda\\Lib\\site-packages\\pandas", prefix="pandas") 21 | a.datas += Tree("D:\\anaconda\\Lib\\site-packages\\scipy", prefix="scipy") 22 | a.datas += Tree("D:\\anaconda\\Lib\\site-packages\\seaborn", prefix="seaborn") 23 | a.datas += Tree("D:\\anaconda\\Lib\\site-packages\\pathlib2", prefix="pathlib") 24 | a.datas += Tree("D:\\anaconda\\Lib\\site-packages\\matplotlib", prefix="matplotlib") 25 | a.datas += Tree("D:\\anaconda\\Lib\\site-packages\\PyQt5", prefix="pyqt") 26 | a.datas += Tree("D:\\anaconda\\Lib\\site-packages\\patsy", prefix="patsy") 27 | 28 | 29 | pyz = PYZ(a.pure, a.zipped_data, 30 | cipher=block_cipher) 31 | exe = EXE(pyz, 32 | a.scripts, 33 | a.binaries, 34 | a.zipfiles, 35 | a.datas, 36 | [], 37 | name='cli', 38 | debug=False, 39 | bootloader_ignore_signals=False, 40 | strip=False, 41 | upx=True, 42 | upx_exclude=[], 43 | runtime_tmpdir=None, 44 | console=True ) 45 | -------------------------------------------------------------------------------- /betsi/data/Zeolite-13X.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 0.00002,2.026477494 3 | 0.0000579,2.541243526 4 | 0.0000907,3.547198566 5 | 0.0000997,5.866784332 6 | 0.000120002,15.07999999 7 | 0.000135878,25.76287669 8 | 0.000149981,40.33 9 | 0.000153726,45.67806745 10 | 0.000162203,64.12239133 11 | 0.000169951,75.52 12 | 0.00017989,84.02434859 13 | 0.000206222,88.10310565 14 | 0.000247599,90.41453575 15 | 0.000279922,98.40000001 16 | 0.00030673,110.3127874 17 | 0.000320061,118.2 18 | 0.000331941,130.1702139 19 | 0.000340176,149.9101801 20 | 0.000410145,162.7 21 | 0.000537827,169.2831879 22 | 0.000900104,175.9 23 | 0.001686888,181.7420809 24 | 0.00209589,183.1710764 25 | 0.002623178,184.5665064 26 | 0.003299735,185.9154892 27 | 0.004185916,187.207991 28 | 0.005255232,188.3560885 29 | 0.006657108,189.4913756 30 | 0.008491008,190.6152549 31 | 0.01080895,191.6753808 32 | 0.013672316,192.6618808 33 | 0.017379148,193.6303044 34 | 0.022120016,194.5457703 35 | 0.027972214,195.4006837 36 | 0.035502441,196.2615519 37 | 0.04518963,197.0870784 38 | 0.057104756,197.8552004 39 | 0.073503814,198.6615603 40 | 0.092897268,199.3819823 41 | 0.117365134,200.0680882 42 | 0.148033492,200.7177207 43 | 0.186991608,201.3422758 44 | 0.236269718,201.9196185 45 | 0.298618506,202.4531402 46 | 0.376375557,202.9157132 47 | 0.475676689,203.3086953 48 | 0.5396658,203.5210373 49 | 0.603274987,203.6931052 50 | 0.644041058,203.8083676 51 | 0.684269297,203.9188626 52 | 0.724374314,204.0281004 53 | 0.764422491,204.1528227 54 | 0.790647003,204.2464609 55 | 0.815998166,204.363534 56 | 0.841457924,204.4757084 57 | 0.866631605,204.6170522 58 | 0.915618717,205.0325277 59 | 0.942539338,205.5096857 60 | 0.967868769,206.3637334 -------------------------------------------------------------------------------- /betsi/data/MIL-101.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 7.24E-05,0.08753826 3 | 0.000143586,4.448491732 4 | 0.000194756,24.30986923 5 | 0.00022592,43.86848354 6 | 0.000262361,63.7515091 7 | 0.000320934,83.63128716 8 | 0.000342678,84.53192965 9 | 0.000410737,98.22305237 10 | 0.000490593,118.09 11 | 0.000529528,128.9174716 12 | 0.000645145,148.7499184 13 | 0.000669906,149.0715688 14 | 0.000824694,168.9379598 15 | 0.001013574,188.7064537 16 | 0.00101671,188.8450428 17 | 0.00130365,207.7157656 18 | 0.00169959,227.3744034 19 | 0.00211443,241.6754578 20 | 0.00259059,254.8634509 21 | 0.003406121,271.8776872 22 | 0.004145448,284.5848683 23 | 0.005452415,302.1592916 24 | 0.006729352,316.2096342 25 | 0.008632363,333.190744 26 | 0.010891724,349.6066038 27 | 0.013800564,366.7296068 28 | 0.017400576,384.1509461 29 | 0.02206847,402.7675775 30 | 0.027823594,422.1405734 31 | 0.034458049,441.6472115 32 | 0.04178732,461.039695 33 | 0.044868541,468.661881 34 | 0.053004415,487.8835366 35 | 0.054801326,491.9567727 36 | 0.065061032,514.1039896 37 | 0.071749888,528.2637419 38 | 0.081785963,551.2667698 39 | 0.088453998,568.1409809 40 | 0.099542319,591.8859035 41 | 0.114468276,610.1018687 42 | 0.139194114,629.6962073 43 | 0.155310622,640.70694 44 | 0.181102132,660.5313581 45 | 0.192015037,686.0379762 46 | 0.206189304,712.0552605 47 | 0.246715808,720.2126766 48 | 0.302737488,727.348092 49 | 0.372465882,733.9154027 50 | 0.474121004,740.9614942 51 | 0.537798314,744.4696542 52 | 0.6024543,747.5405809 53 | 0.643579302,749.3566697 54 | 0.684178419,751.0499752 55 | 0.724252702,752.7013834 56 | 0.76441783,754.3729568 57 | 0.790465116,755.4935687 58 | 0.815945002,756.6426051 59 | 0.841290606,757.8866495 60 | 0.866639427,759.2780631 61 | 0.913918632,762.7669094 62 | 0.941891352,766.2296465 63 | 0.965929407,772.2981292 -------------------------------------------------------------------------------- /betsi/data/UiO-66-NH2.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 0.000000848,19.5368906 3 | 0.00000229,39.26330117 4 | 0.00000569,58.89848944 5 | 0.0000104,78.53675968 6 | 0.0000212,98.16156468 7 | 0.0000417,117.7725542 8 | 0.0000881,137.364894 9 | 0.00019284,156.8928206 10 | 0.000441225,176.3458212 11 | 0.000989339,194.9833077 12 | 0.001921761,210.941578 13 | 0.00291461,221.4565494 14 | 0.003944906,229.2971183 15 | 0.004813169,234.4486662 16 | 0.00594435,239.9628281 17 | 0.006835363,243.6246739 18 | 0.007914356,247.5072786 19 | 0.008883336,250.6453829 20 | 0.009932936,253.7236773 21 | 0.01909818,275.1052661 22 | 0.029132958,293.9211205 23 | 0.038429457,310.3121552 24 | 0.050828881,325.7113692 25 | 0.057356098,331.6109696 26 | 0.069821708,340.1961137 27 | 0.0802729,345.4545957 28 | 0.090591613,349.2613875 29 | 0.100871653,352.2163788 30 | 0.145607594,360.6474742 31 | 0.204338998,367.2167836 32 | 0.256823283,371.5722959 33 | 0.307448459,375.18 34 | 0.355311849,378.2737613 35 | 0.400438035,380.9933042 36 | 0.450466992,383.8371455 37 | 0.500562855,386.59486 38 | 0.550628559,389.3255242 39 | 0.600467466,392.1355654 40 | 0.650693889,395.113767 41 | 0.70078011,398.3998811 42 | 0.749541023,402.0117645 43 | 0.799036389,406.4862224 44 | 0.809765349,407.7992544 45 | 0.820897926,409.0453038 46 | 0.830182771,410.1992839 47 | 0.840517528,411.5328422 48 | 0.850135475,412.8666983 49 | 0.860012207,414.3654304 50 | 0.870324132,416.0635373 51 | 0.880063477,417.8667216 52 | 0.890036621,419.9242135 53 | 0.899697316,422.2159538 54 | 0.910224633,425.0406414 55 | 0.920571549,428.2501146 56 | 0.929731796,431.8828211 57 | 0.939225811,436.3450083 58 | 0.948782819,442.3544504 59 | 0.95900507,450.9633684 60 | 0.968527785,463.1038984 61 | 0.97741276,482.2617709 62 | 0.986178212,513.237696 63 | 0.992911451,555.9569992 -------------------------------------------------------------------------------- /betsi/data/EXP_ZIF-8powder.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po),Quantity Adsorbed (cm³/g STP) 2 | 4.19974E-05,20.28274449 3 | 9.96909E-05,40.67296372 4 | 0.000148121,60.87679503 5 | 0.000188506,81.25108951 6 | 0.000224278,101.619451 7 | 0.000259844,122.0302949 8 | 0.000299953,142.3989108 9 | 0.000349057,162.7862552 10 | 0.000417927,183.0768101 11 | 0.000521085,203.3467253 12 | 0.000684773,223.5674918 13 | 0.000949862,243.5863528 14 | 0.001422718,263.1878155 15 | 0.00193056,275.0843134 16 | 0.002936687,288.4217818 17 | 0.003871099,295.8545087 18 | 0.004945074,301.7994394 19 | 0.005730774,305.3298612 20 | 0.006698907,312.6281648 21 | 0.008054492,323.3365919 22 | 0.008892109,328.0720786 23 | 0.009781263,332.3208478 24 | 0.02074716,356.4611424 25 | 0.029110932,364.3844726 26 | 0.038163686,400.8022049 27 | 0.04836001,414.1358694 28 | 0.063774299,420.0631319 29 | 0.069776865,421.7029946 30 | 0.08004303,424.0256576 31 | 0.090083618,425.9749438 32 | 0.10043044,427.7245243 33 | 0.145027143,433.4843278 34 | 0.202710154,438.9966576 35 | 0.254860289,443.1539479 36 | 0.305344376,446.72724 37 | 0.354387044,449.9327603 38 | 0.399269895,452.6201371 39 | 0.449413315,455.3270013 40 | 0.499421734,457.8319896 41 | 0.549130569,460.2240887 42 | 0.599553456,462.617951 43 | 0.648965909,465.067526 44 | 0.699055532,467.8544535 45 | 0.748964062,471.0471959 46 | 0.7992439,474.8147805 47 | 0.809235821,475.7689968 48 | 0.819317209,476.7695132 49 | 0.829266798,477.7855803 50 | 0.839403863,478.8588776 51 | 0.850174553,480.0949458 52 | 0.859575733,481.2178317 53 | 0.869079932,482.5717543 54 | 0.879065428,484.1244541 55 | 0.889536882,485.8798938 56 | 0.899627608,487.7359597 57 | 0.90986306,489.8854979 58 | 0.919854649,492.4174808 59 | 0.929147394,495.2036614 60 | 0.939055955,498.7431648 61 | 0.948884511,503.2296235 62 | 0.959088154,509.3877026 63 | 0.969051619,517.7201809 64 | 0.978076108,529.0286753 65 | 0.986984423,548.9853612 66 | 0.994455819,585.4406004 -------------------------------------------------------------------------------- /betsi/data/HKUST-1.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 4.60E-07,29.24223573 3 | 8.08E-07,58.6913572 4 | 1.24E-06,88.1822873 5 | 1.95E-06,117.7708096 6 | 3.47E-06,147.0208837 7 | 1.98E-05,176.6272698 8 | 3.01E-05,188.1 9 | 3.99E-05,197.3 10 | 4.84E-05,205.7335818 11 | 6.00E-05,219.4 12 | 7.19E-05,234.899105 13 | 8.00E-05,247.5 14 | 9.10E-05,263.9058595 15 | 0.000122165,292.8992342 16 | 0.000169951,309.4 17 | 0.000240034,320.8391657 18 | 0.000469896,333.9 19 | 0.001029937,346.7523766 20 | 0.002010342,351.561349 21 | 0.003045762,354.0055815 22 | 0.003948507,355.4263289 23 | 0.004830186,356.484218 24 | 0.005912671,357.5251441 25 | 0.006904737,358.3168714 26 | 0.007945685,359.0361979 27 | 0.00893684,359.633011 28 | 0.009950202,360.2001188 29 | 0.019566394,363.8561184 30 | 0.02937333,366.7213338 31 | 0.039573751,369.6 32 | 0.053048755,373.5872441 33 | 0.059782481,375.5479842 34 | 0.070285857,377.6609013 35 | 0.080382115,379.2731153 36 | 0.090693352,380.7148902 37 | 0.100863524,381.9894138 38 | 0.145578459,386.4698639 39 | 0.201715088,390.404903 40 | 0.255897983,393.2731922 41 | 0.306354706,395.4325597 42 | 0.355727318,396.994765 43 | 0.400710563,398.472277 44 | 0.450996492,399.9312104 45 | 0.500912037,401.2288073 46 | 0.550729386,402.3886749 47 | 0.600539736,403.4473961 48 | 0.650423732,404.4381916 49 | 0.699409212,405.3195484 50 | 0.749430462,406.161677 51 | 0.799589806,406.9765303 52 | 0.810603847,407.2760134 53 | 0.820750466,407.4391274 54 | 0.830251162,407.5884184 55 | 0.840610582,407.7410674 56 | 0.850090332,407.8706136 57 | 0.860139894,408.0103314 58 | 0.870536548,408.1640723 59 | 0.880439116,408.3117647 60 | 0.890351942,408.4410411 61 | 0.900085449,408.5758706 62 | 0.909885718,408.7267665 63 | 0.920408124,408.8781608 64 | 0.930173819,409.027047 65 | 0.940068359,409.1678296 66 | 0.949686839,409.3365257 67 | 0.959833706,409.4983328 68 | 0.969298233,409.6727584 69 | 0.979271939,409.8817061 70 | 0.989260685,410.1012315 71 | 0.998416184,410.5466624 -------------------------------------------------------------------------------- /betsi/data/DMOF-1.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 0.0000475,0.291525027 3 | 0.0000969,0.57683055 4 | 0.000123181,0.620048828 5 | 0.000128577,0.658135595 6 | 0.000164533,0.95282625 7 | 0.000217301,5.996396582 8 | 0.000257214,15.96630557 9 | 0.000279122,25.93740552 10 | 0.000300744,35.90617865 11 | 0.000322968,45.87471218 12 | 0.000351409,56.18553815 13 | 0.000368692,66.15244743 14 | 0.000383691,76.11937752 15 | 0.000393716,86.08662015 16 | 0.000410668,96.05266401 17 | 0.000431396,106.0217067 18 | 0.000433515,115.9905568 19 | 0.000434096,125.9564861 20 | 0.00044681,135.9195637 21 | 0.000448218,155.8530741 22 | 0.000460131,212.5 23 | 0.000469896,242.8 24 | 0.000479868,263.6 25 | 0.000495921,293.5893529 26 | 0.000505841,312.6029111 27 | 0.000522755,322.5586345 28 | 0.000561279,332.5075569 29 | 0.000612371,351.4762077 30 | 0.000762905,361.3968901 31 | 0.001046492,371.2898879 32 | 0.001338775,376.7 33 | 0.001690835,381.1722028 34 | 0.002200656,385.8 35 | 0.002830961,390.9188471 36 | 0.003348623,396.2006375 37 | 0.003953084,406.0520849 38 | 0.004340588,415.523028 39 | 0.005364889,425.1448932 40 | 0.006599917,430.6897216 41 | 0.008409006,435.1444608 42 | 0.010647117,438.35 43 | 0.01347842,440.7835726 44 | 0.017400821,442.8405229 45 | 0.022037446,444.346239 46 | 0.028872495,445.7078586 47 | 0.037948501,446.8042356 48 | 0.043221172,447.2464063 49 | 0.056330468,448.001404 50 | 0.075704826,448.6718223 51 | 0.096747454,449.1243456 52 | 0.12106687,449.47 53 | 0.148103775,449.7321326 54 | 0.182104311,449.9648932 55 | 0.230197892,450.2006076 56 | 0.294028489,450.4356866 57 | 0.376152789,450.6699096 58 | 0.475149693,450.9570748 59 | 0.539634859,451.200884 60 | 0.602891293,451.544312 61 | 0.644112905,451.8738368 62 | 0.683964225,452.333743 63 | 0.723676637,453.0147327 64 | 0.762441498,454.1258688 65 | 0.789400207,455.3847044 66 | 0.815016979,457.0811051 67 | 0.840145607,459.2774529 68 | 0.86521186,462.0303932 69 | 0.93003418,467.3296084 70 | 0.958849806,467.7860947 71 | 0.98337829,468.3480288 -------------------------------------------------------------------------------- /betsi/data/MOF-5.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 4.88E-05,1.035999212 3 | 0.000103039,1.908035393 4 | 0.000152256,2.155714654 5 | 0.000192992,2.701018481 6 | 0.000227689,3.151022853 7 | 0.000262075,3.805326161 8 | 0.000339317,17.8541585 9 | 0.000410328,37.67918299 10 | 0.000436056,44.47981173 11 | 0.000530897,64.29315122 12 | 0.00068157,83.51374464 13 | 0.000825555,91.83641416 14 | 0.001018034,98.67791888 15 | 0.001387476,112.6054566 16 | 0.001754256,124.3881237 17 | 0.002082039,134.3365982 18 | 0.002634584,154.1134249 19 | 0.003096,173.9258918 20 | 0.003421564,190.6924562 21 | 0.00373714,210.4792227 22 | 0.003990249,230.2631753 23 | 0.004203087,250.0246113 24 | 0.004384442,269.7867715 25 | 0.004550507,289.5232301 26 | 0.004701429,309.269059 27 | 0.004831217,329.0177787 28 | 0.004959268,348.7242874 29 | 0.005083147,368.4494647 30 | 0.005198735,388.1651011 31 | 0.005324749,407.8353851 32 | 0.005452929,427.5402802 33 | 0.005573198,447.2241306 34 | 0.005699939,466.9447989 35 | 0.005873153,486.6044365 36 | 0.006055369,506.4382458 37 | 0.006277627,526.0839057 38 | 0.006538265,545.7713291 39 | 0.006870036,565.4039292 40 | 0.007278209,585.0635997 41 | 0.007840088,604.6021745 42 | 0.008534543,623.8435096 43 | 0.009537856,643.1983601 44 | 0.01071683,659.8969769 45 | 0.012716909,679.3168505 46 | 0.013356843,683.8118561 47 | 0.016783566,703.1560794 48 | 0.017190992,704.6365863 49 | 0.021664124,719.7607782 50 | 0.029557787,736.5798583 51 | 0.037496831,747.2871359 52 | 0.043815956,753.5464095 53 | 0.056354812,763.0255306 54 | 0.075000773,772.764789 55 | 0.094622889,780.0294297 56 | 0.12134011,787.3459355 57 | 0.148705066,793.2239549 58 | 0.186663973,799.9997939 59 | 0.233461347,807.52346 60 | 0.292643557,816.2340696 61 | 0.376872515,825.7953808 62 | 0.477271717,831.2825856 63 | 0.557467879,832.9012232 64 | 0.622324826,833.3172592 65 | 0.663688643,833.2265224 66 | 0.703952672,832.993448 67 | 0.744084723,832.6912456 68 | 0.784234934,832.31166 69 | 0.810369834,831.9487744 70 | 0.835704039,831.6707888 71 | 0.86122081,831.3101984 72 | 0.886535362,830.9686392 73 | 0.936028809,830.3224928 74 | 0.962543788,830.0199416 75 | 0.987971674,829.8951608 -------------------------------------------------------------------------------- /betsi/data/NU-1105.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 0.000145,42.426 3 | 0.000228,66.6694 4 | 0.000249,72.7303 5 | 0.00029,84.852 6 | 0.000311,90.9128 7 | 0.000353,103.035 8 | 0.000436,127.278 9 | 0.003921,145.501 10 | 0.004004,169.7449999 11 | 0.004149,212.171 12 | 0.004232,236.414 13 | 0.004315,260.6579999 14 | 0.00778,272.8199999 15 | 0.007842,291.003 16 | 0.011307,303.166 17 | 0.014793,321.3890001 18 | 0.01832,351.734 19 | 0.021785,363.897 20 | 0.02527,382.12 21 | 0.028735,394.2830001 22 | 0.03222,412.507 23 | 0.035706,430.7300001 24 | 0.039171,442.8929999 25 | 0.042615,448.995 26 | 0.0461,467.218 27 | 0.049565,479.381 28 | 0.05303,491.543 29 | 0.053071,503.665 30 | 0.056536,515.828 31 | 0.05998,521.93 32 | 0.066868,534.133 33 | 0.070333,546.296 34 | 0.077242,564.56 35 | 0.080686,570.662 36 | 0.084171,588.886 37 | 0.087615,594.988 38 | 0.091101,613.2110001 39 | 0.09803,637.5359999 40 | 0.104939,655.8009999 41 | 0.115333,692.289 42 | 0.129089,710.635 43 | 0.132595,734.92 44 | 0.139525,759.2450001 45 | 0.149878,783.611 46 | 0.16023,807.9770001 47 | 0.17743,832.426 48 | 0.191247,868.9550001 49 | 0.205003,887.301 50 | 0.215335,905.606 51 | 0.222223,917.81 52 | 0.225709,936.033 53 | 0.232617,954.298 54 | 0.236144,984.643 55 | 0.239651,1008.93 56 | 0.239734,1033.17 57 | 0.239816,1057.41 58 | 0.243302,1075.64 59 | 0.243406,1105.94 60 | 0.246892,1124.17 61 | 0.257285,1160.65 62 | 0.264153,1166.8 63 | 0.274485,1185.1 64 | 0.281332,1185.18 65 | 0.284796,1197.35 66 | 0.295108,1209.59 67 | 0.305419,1221.84 68 | 0.319153,1234.12 69 | 0.329486,1252.43 70 | 0.346644,1264.75 71 | 0.356935,1270.94 72 | 0.367225,1277.12 73 | 0.387806,1289.49 74 | 0.40152,1295.71 75 | 0.415234,1301.94 76 | 0.425503,1302.06 77 | 0.44264,1308.33 78 | 0.463201,1314.63 79 | 0.483762,1320.94 80 | 0.497475,1327.16 81 | 0.518015,1327.41 82 | 0.538596,1339.779999 83 | 0.548866,1339.9 84 | 0.559136,1340.020001 85 | 0.565982,1340.1 86 | 0.583119,1346.37 87 | 0.596813,1346.529999 88 | 0.631066,1353 89 | 0.65505,1359.35 90 | 0.692727,1365.86 91 | 0.713267,1366.11 92 | 0.73723,1366.4 93 | 0.764616,1366.720001 94 | 0.764637,1372.780001 95 | 0.792043,1379.17 96 | 0.81943,1379.5 97 | 0.822875,1385.6 98 | 0.857107,1386.01 99 | 0.891381,1398.540001 100 | 0.915344,1398.83 101 | 0.918788,1404.93 102 | 0.956444,1405.38 103 | 0.966735,1411.56 104 | 0.966756,1417.63 105 | 0.980469,1423.85 106 | 0.990781,1436.09 107 | 0.994225,1442.2 -------------------------------------------------------------------------------- /betsi/data/MIL-100.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 0.001034,197.1289999 3 | 0.001108417,199.5241455 4 | 0.00118819,202.0821304 5 | 0.001273703,204.8119929 6 | 0.001365372,207.7227303 7 | 0.001463638,210.8231249 8 | 0.001568975,214.1215167 9 | 0.001681894,217.6255089 10 | 0.00180294,221.3415887 11 | 0.001932698,225.2746433 12 | 0.002071794,229.4273454 13 | 0.002220901,233.7993736 14 | 0.002380739,238.3864309 15 | 0.00255208,243.1790121 16 | 0.002735753,248.1608543 17 | 0.002932645,253.3070025 18 | 0.003143708,258.5813915 19 | 0.00336996,263.9338311 20 | 0.003612496,269.2962544 21 | 0.003872487,274.5780503 22 | 0.004151189,279.6602664 23 | 0.00444995,284.3884148 24 | 0.004770213,288.5635505 25 | 0.005113525,292.0917185 26 | 0.005481545,295.4880853 27 | 0.005876051,298.8025947 28 | 0.00629895,302.0144892 29 | 0.006752285,305.1071346 30 | 0.007238246,308.0709686 31 | 0.007759182,310.9074818 32 | 0.00831761,313.6345179 33 | 0.008916228,316.2932685 34 | 0.009557928,318.952217 35 | 0.010245811,321.6227374 36 | 0.010983202,324.287628 37 | 0.011773662,326.9384946 38 | 0.012621011,329.5701329 39 | 0.013529344,332.1824813 40 | 0.01450305,334.783238 41 | 0.015546834,337.3913298 42 | 0.016665738,340.0414743 43 | 0.01786517,342.7901286 44 | 0.019150925,345.7007472 45 | 0.020529216,348.6742774 46 | 0.022006702,351.6818036 47 | 0.023590522,354.7289731 48 | 0.02528833,357.8292806 49 | 0.02710833,361.0073584 50 | 0.029059314,364.303283 51 | 0.031150711,367.7567135 52 | 0.033392625,371.1060915 53 | 0.03579589,374.45872 54 | 0.038372117,378.2027532 55 | 0.041133755,382.8800127 56 | 0.044094149,388.543566 57 | 0.047267601,395.5080957 58 | 0.050669447,404.618763 59 | 0.054316123,416.0045976 60 | 0.05822525,424.1716129 61 | 0.062415717,430.9169639 62 | 0.066907771,436.1151516 63 | 0.071723119,440.0867187 64 | 0.076885026,445.2976561 65 | 0.082418435,451.4351027 66 | 0.088350084,458.5978572 67 | 0.094708633,466.4460405 68 | 0.101524806,472.6192715 69 | 0.108831538,479.6663773 70 | 0.116664136,491.1593287 71 | 0.125060445,506.1904901 72 | 0.134061035,523.4437608 73 | 0.143709398,530.6339937 74 | 0.154052152,533.1608508 75 | 0.165139274,536.3349618 76 | 0.177024334,540.5861805 77 | 0.189764762,541.0808972 78 | 0.203422118,543.6179283 79 | 0.218062392,543.7850362 80 | 0.233756326,545.8906345 81 | 0.25057975,548.8538363 82 | 0.268613956,549.48699 83 | 0.287946082,551.5766383 84 | 0.308669539,552.5692394 85 | 0.330884462,553.5069729 86 | 0.354698192,555.5232089 87 | 0.380225793,556.6596188 88 | 0.407590615,558.3361394 89 | 0.43692488,559.6240671 90 | 0.46837033,560.1315564 91 | 0.502078907,562.1355582 92 | 0.538213488,563.9579248 93 | 0.576948671,564.5252933 94 | 0.618471623,565.8052343 95 | 0.662982978,566.6433628 96 | 0.710697812,568.4923111 97 | 0.76184668,570.9781344 98 | 0.816676727,571.4921361 99 | 0.875452888,572.482785 100 | 0.938459166,574.9414501 -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /betsi/.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /betsi/data/NU-1104.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 0.002546,85.6082 3 | 0.005092,238.784 4 | 0.006288706,261.9 5 | 0.007638,279.347 6 | 0.009022805,295.4 7 | 0.010183,306.397 8 | 0.012729,324.438 9 | 0.015275,342.478 10 | 0.017821,347.005 11 | 0.020367,378.559 12 | 0.022913,396.6 13 | 0.025459,410.136 14 | 0.028004,441.69 15 | 0.03055,459.731 16 | 0.033096,468.763 17 | 0.035642,477.794 18 | 0.038188,491.331 19 | 0.040734,504.867 20 | 0.045825,522.93 21 | 0.050917,559.011 22 | 0.053463,572.547 23 | 0.056009,581.579 24 | 0.058555,599.62 25 | 0.0611,640.183 26 | 0.063646,667.233 27 | 0.066192,676.264 28 | 0.068738,707.818 29 | 0.071284,730.364 30 | 0.07383,752.909 31 | 0.076376,811.49 32 | 0.078921,829.531 33 | 0.081467,852.076 34 | 0.084013,892.639 35 | 0.086559,906.175 36 | 0.091651,942.256 37 | 0.094197,960.297 38 | 0.096742,978.338 39 | 0.099288,991.874 40 | 0.101835,1000.91 41 | 0.10438,1014.44 42 | 0.106926,1027.98 43 | 0.112018,1041.54 44 | 0.114563,1059.58 45 | 0.119655,1086.65 46 | 0.122201,1091.18 47 | 0.124747,1104.71 48 | 0.127293,1118.25 49 | 0.129839,1127.28 50 | 0.132384,1136.31 51 | 0.13493,1154.35 52 | 0.137476,1167.89 53 | 0.142568,1181.45 54 | 0.145114,1190.48 55 | 0.150205,1204.04 56 | 0.152751,1208.57 57 | 0.155297,1222.1 58 | 0.160388,1231.16 59 | 0.162935,1244.69 60 | 0.165481,1253.72 61 | 0.168026,1262.76 62 | 0.173118,1285.32 63 | 0.175664,1294.35 64 | 0.178209,1307.89 65 | 0.180756,1321.43 66 | 0.183302,1334.96 67 | 0.185847,1348.5 68 | 0.188394,1362.06 69 | 0.190939,1371.04 70 | 0.193485,1411.63 71 | 0.19603,1452.19 72 | 0.198577,1524.29 73 | 0.201122,1542.33 74 | 0.203668,1564.87 75 | 0.206215,1578.41 76 | 0.20876,1591.95 77 | 0.211306,1609.99 78 | 0.213851,1628.03 79 | 0.216398,1641.56 80 | 0.218943,1650.6 81 | 0.224036,1664.15 82 | 0.226581,1673.19 83 | 0.231673,1700.26 84 | 0.234219,1713.79 85 | 0.23931,1722.85 86 | 0.241856,1727.38 87 | 0.25204,1736.48 88 | 0.259677,1741.05 89 | 0.264769,1750.1 90 | 0.269861,1754.65 91 | 0.272406,1754.67 92 | 0.277498,1759.22 93 | 0.282589,1763.77 94 | 0.285136,1763.8 95 | 0.290227,1763.84 96 | 0.297865,1768.41 97 | 0.30041,1772.94 98 | 0.308048,1773.01 99 | 0.310595,1777.54 100 | 0.315686,1777.58 101 | 0.320778,1782.13 102 | 0.325869,1782.18 103 | 0.330961,1782.22 104 | 0.34369,1786.84 105 | 0.351328,1791.41 106 | 0.35642,1791.46 107 | 0.364057,1796.03 108 | 0.369149,1796.07 109 | 0.376786,1796.14 110 | 0.381878,1800.69 111 | 0.38697,1800.74 112 | 0.402245,1800.87 113 | 0.407337,1800.92 114 | 0.412428,1800.96 115 | 0.425158,1801.08 116 | 0.432796,1805.65 117 | 0.440432,1805.72 118 | 0.455708,1810.36 119 | 0.465891,1810.45 120 | 0.488804,1815.16 121 | 0.501533,1819.77 122 | 0.511717,1819.87 123 | 0.516808,1819.91 124 | 0.524446,1824.48 125 | 0.529538,1824.53 126 | 0.537176,1824.6 127 | 0.542267,1824.64 128 | 0.549905,1824.71 129 | 0.560088,1824.8 130 | 0.570271,1824.89 131 | 0.580454,1829.49 132 | 0.593184,1834.1 133 | 0.600822,1834.17 134 | 0.611005,1834.26 135 | 0.623734,1834.37 136 | 0.631372,1838.95 137 | 0.636464,1838.99 138 | 0.654285,1843.66 139 | 0.661922,1843.72 140 | 0.672106,1843.81 141 | 0.687381,1843.95 142 | 0.689927,1843.97 143 | 0.702655,1848.59 144 | 0.710293,1848.66 145 | 0.715385,1848.7 146 | 0.733206,1848.86 147 | 0.735752,1848.88 148 | 0.745935,1848.97 149 | 0.753573,1849.04 150 | 0.763756,1849.13 151 | 0.768848,1849.18 152 | 0.77394,1853.73 153 | 0.779031,1853.77 154 | 0.784123,1853.82 155 | 0.796852,1853.93 156 | 0.81722,1854.11 157 | 0.832494,1858.75 158 | 0.840132,1858.82 159 | 0.847769,1858.89 160 | 0.863045,1863.53 161 | 0.868136,1863.57 162 | 0.87832,1863.67 163 | 0.885957,1863.73 164 | 0.903778,1863.89 165 | 0.957242,1868.87 166 | 0.964878,1868.94 167 | 0.987791,1873.65 168 | 0.992883,1878.2 -------------------------------------------------------------------------------- /betsi/data/UiO-66.csv: -------------------------------------------------------------------------------- 1 | Relative Pressure (P/Po), Quantity Adsorbed (cm3/g STP) 2 | 1.23E-07,2.514208097 3 | 2.48E-07,5.037251954 4 | 3.50E-07,7.558244677 5 | 5.48E-07,10.08585684 6 | 7.55E-07,12.59727996 7 | 9.99E-07,15.10458709 8 | 1.22E-06,17.63878786 9 | 1.47E-06,20.16339997 10 | 1.97E-06,22.69132411 11 | 2.84E-06,25.2214746 12 | 3.85E-06,27.73191927 13 | 5.17E-06,30.25412583 14 | 6.88E-06,32.77569526 15 | 8.83E-06,35.29920653 16 | 1.14E-05,37.83511452 17 | 1.44E-05,40.36452153 18 | 1.78E-05,42.88055967 19 | 2.15E-05,45.41154666 20 | 2.54E-05,47.9453635 21 | 2.91E-05,50.45887736 22 | 3.30E-05,52.99521562 23 | 3.67E-05,55.50982846 24 | 4.04E-05,58.04535971 25 | 4.40E-05,60.58747066 26 | 4.76E-05,63.12764682 27 | 5.12E-05,65.66821784 28 | 5.48E-05,68.20299503 29 | 5.86E-05,70.74937929 30 | 6.25E-05,73.27186156 31 | 6.65E-05,75.79976796 32 | 7.07E-05,78.3308866 33 | 7.53E-05,80.86536352 34 | 7.99E-05,83.38577928 35 | 8.49E-05,85.92587128 36 | 9.02E-05,88.45030224 37 | 9.59E-05,90.98016072 38 | 0.000101866,93.50766048 39 | 0.00010843,96.02943816 40 | 0.000115669,98.56759392 41 | 0.000123923,101.0975406 42 | 0.000132532,103.6438156 43 | 0.000142085,106.1716869 44 | 0.000152225,108.6991722 45 | 0.000163528,111.2194978 46 | 0.000175053,113.7443723 47 | 0.000188404,116.2740431 48 | 0.000203102,118.7988271 49 | 0.000219533,121.3245868 50 | 0.000237273,123.8480286 51 | 0.000256856,126.3777168 52 | 0.000278382,128.9050681 53 | 0.000302122,131.4281643 54 | 0.000328336,133.9662684 55 | 0.000357464,136.4882939 56 | 0.000389421,139.0076204 57 | 0.000425039,141.5317433 58 | 0.00046396,144.0385752 59 | 0.000507494,146.5478842 60 | 0.000555773,149.0602994 61 | 0.000609421,151.5697608 62 | 0.000668535,154.060653 63 | 0.000733715,156.5337288 64 | 0.000805316,158.9949881 65 | 0.0008836,161.4439358 66 | 0.000969555,163.8718429 67 | 0.001180284,168.6 68 | 0.001369924,172.5 69 | 0.001659897,177.7 70 | 0.001983868,182.6426257 71 | 0.002481232,188.8 72 | 0.0029778,193.9555603 73 | 0.003900276,202.0860302 74 | 0.004901601,209.4802401 75 | 0.005748579,214.9546489 76 | 0.006815202,220.9544905 77 | 0.007932263,226.4712673 78 | 0.008908562,230.7932965 79 | 0.009924807,234.9431672 80 | 0.012216106,242 81 | 0.015344472,250.3 82 | 0.020657011,260.2120996 83 | 0.029153998,267.6667152 84 | 0.043038331,274.4918795 85 | 0.05607993,278.4784614 86 | 0.060133027,279.4787193 87 | 0.070310191,281.615879 88 | 0.080444318,283.3976849 89 | 0.090170995,284.8714471 90 | 0.100546237,286.2448034 91 | 0.110563966,287.4462928 92 | 0.120602203,288.5181411 93 | 0.130585935,289.5010852 94 | 0.140095215,290.39 95 | 0.150374866,291.2636477 96 | 0.160507882,292.0511271 97 | 0.170667982,292.8048628 98 | 0.179965883,293.4867474 99 | 0.190645793,294.2087023 100 | 0.200301599,294.8255949 101 | 0.210335014,295.4385895 102 | 0.220487623,296.0307331 103 | 0.230573416,296.5861908 104 | 0.240846958,297.1541411 105 | 0.25056775,297.6650242 106 | 0.260794834,298.1822797 107 | 0.269580715,298.6360556 108 | 0.280301794,299.1344551 109 | 0.290696258,299.6123679 110 | 0.300168734,299.9998531 111 | 0.319236927,300.8437555 112 | 0.340946451,301.7427779 113 | 0.360937434,302.53696 114 | 0.38070078,303.2960293 115 | 0.401083839,304.0663297 116 | 0.421098485,304.7946982 117 | 0.440714586,305.5083048 118 | 0.460879323,306.2269201 119 | 0.479423448,306.9263143 120 | 0.500683264,307.6755514 121 | 0.519294854,308.373187 122 | 0.540988798,309.1130853 123 | 0.559146623,309.8063301 124 | 0.580857521,310.5774736 125 | 0.600769168,311.3005092 126 | 0.620648506,312.0202711 127 | 0.640945441,312.7833075 128 | 0.660586966,313.5369189 129 | 0.680476479,314.3573156 130 | 0.69949284,315.2304864 131 | 0.719410474,316.1909329 132 | 0.739506409,317.1909509 133 | 0.759642624,318.3008609 134 | 0.779642839,319.4802959 135 | 0.799644973,320.7616671 136 | 0.809048329,321.4671465 137 | 0.820769307,322.3134339 138 | 0.829732712,323.0572258 139 | 0.83925335,323.9152679 140 | 0.850505865,324.9141619 141 | 0.860630845,325.9756673 142 | 0.869346002,326.9457206 143 | 0.880763469,328.290526 144 | 0.889852706,329.500242 145 | 0.90087879,331.1254877 146 | 0.909388657,332.5817953 147 | 0.919315551,334.4979075 148 | 0.929206017,336.7771364 149 | 0.93917914,339.6217573 150 | 0.949129528,343.3265922 151 | 0.986264135,404.2262582 152 | 0.989723137,439.3506036 153 | 0.992201704,501.9726082 -------------------------------------------------------------------------------- /.idea/markdown-navigator-enh.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /betsi/.idea/markdown-navigator-enh.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2 |

3 | A2ML 4 |

5 | 6 |

7 | 8 | [![Build Status](https://travis-ci.com/nakulrampal/betsi-gui.svg?token=Z8uG4PAMYmS7Xn1zqF5i&branch=master)](https://travis-ci.com/nakulrampal/betsi-gui) 9 | [![Documentation Status](https://readthedocs.org/projects/aamplify/badge/?version=latest)](https://aamplify.readthedocs.io/en/latest/?badge=latest) 10 | [![Gitter](https://badges.gitter.im/betsi-gui/community.svg)](https://gitter.im/betsi-gui/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Commit Activity](https://img.shields.io/github/commit-activity/m/nakulrampal/betsi-gui)](https://github.com/nakulrampal/betsi-gui/pulse) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/nakulrampal/github-gui/LICENSE.txt) 11 | 12 |

13 | 14 |

15 | banner 16 |

17 | 18 | ## Software Requirements: 19 | 20 | ### OS Requirements 21 | This package is supported for *windows*, *macOS* and *Linux*. The package has been tested on the following systems: 22 | + Windows: 10 (10.0.19041) 23 | + macOS: Mojave (10.14.1) 24 | + Linux: Ubuntu 16.04 25 | 26 | ### Python Dependencies 27 | 28 | `betsi-gui` mainly depends on the Python scientific stack. 29 | 30 | ``` 31 | numpy==1.19.3 32 | scipy==1.5.4 33 | matplotlib==3.2.2 34 | PyQt5==5.9.2 35 | pandas==1.1.5 36 | seaborn==0.11.0 37 | statsmodels==0.12.1 38 | ``` 39 | ## Running BETSI from an executable 40 | 41 | Download the executables for *Windows* or *Linux* found in the repositories run them on your machine. This will automatically run the code for you and take you immediately to the [Instructions of use](#instructions-of-use) found below. If instead you wish to download the source code and install BETSI on your machine, please follow the steps below. 42 | 43 | ## Steps to install BETSI 44 | 45 | *Estimated installation time*: ***10 minutes*** 46 | 47 | Download Anaconda from https://anaconda.org for your operating system. Once you have done so, open the Anaconda Navigator program. 48 | 49 | # step-1 50 | 51 | Next, create a new environment by clicking ```Create``` on the bottom left corner. You can give your environment and arbitrary name (we have called ours ```betsi```) and select as a package ```Python 3.7```. 52 | 53 | # step-2 54 | 55 | If you have successfully created a new environment, it should appear under the base environment. Next, click the :arrow_forward: button in the newly created environment and select ```Open Terminal``` 56 | 57 | # step-4 58 | 59 | This will prompt a command terminal in the new environment. 60 | 61 | # step-5 62 | 63 | ### :star: Option 1: via ```pypi``` 64 | 65 | Next, type in the command: ```pip install -i https://test.pypi.org/simple/ betsi-gui``` 66 | 67 | step-6 68 | 69 | ### :point_right: Option 2: using the source code 70 | 71 | Navigate to the location where you have stored the source code and enter the directory ```betsi-gui```. Once in the directory, run the command ```python setup.py install```, followed by the command ```pip install .``` 72 | 73 | To read more about using the command line please visit: [windows](https://www.computerhope.com/issues/chusedos.htm), [linux](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview), [macOS](https://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line). 74 | 75 | # 76 | :heavy_check_mark: This will install BETSI in the newly created environment and download all the relevant python packages from our test server. 77 | 78 | ## Instructions of use 79 | 80 | *Estimated run time*: ***5 minutes*** 81 | 82 | Next, to run BETSI, type in the command: ```python -m betsi``` 83 | 84 | # step-7 85 | 86 | Run the command, which will prompt the BETSI GUI. This step may take some time. The BETSI GUI will appear with its default settings as laid out in the Rouquerol criteria. Run an isotherm in the GUI by dragging a correct ```.csv``` file into the empty space on the right. Test isotherms can be found in the repository. Note that isotherms will only run successfully in BETSI if they are in the same format as the exemplary isotherms, further information can be found in section [Test Dataset](#test-dataset) below. 87 | 88 | # step-9 89 | 90 | The code will run automatically and two windows appear. For a full explanation of all figures, please refer to the Supplementary Information of the manuscript, Section S5. 91 | 92 | # step-10 93 | 94 | Further, you can interact with the GUI by manually selecting other Rouquerol-permitted BET areas. In the ```Filtered BET areas``` plot, click on one of the other points. All plots will automatically update to the new selected linear region/BET area. The ```active``` plot is always shown in yellow. 95 | 96 | # step-11 97 | 98 | To output BETSI data, select an output directory and click ```Export Results``` in the GUI. 99 | 100 | # step-12 101 | 102 | The specified directory will contain ```.pdf``` prints of the two active plots (BETSI analysis and regression diagnostics), a ```.json``` file specifying the filter criteria, a ```.txt``` file featuring a small summary, and a folder containing all matrices that the program uses. 103 | 104 | # step-14 105 | 106 | Analyse a new isotherm in BETSI by clearing the current plot either via ```Tools-> Clear```, or by pressing the hotkey combination ```CMD/CTRL+C```. 107 | 108 | # step-13 109 | 110 | 111 | ## Test Dataset 112 | 113 | A test dataset of isotherms is supplied on this repository. To run the isotherms in BETSI, download the dataset and drag isotherms into the BETSI GUI as described above. If you would like to try BETSI with your own dataset, you will need to convert it first into the same format as the test isotherms: It must be a 2-column ```.csv``` file with the relative pressure in the first column and the adsorbed quantity in the second. The first row will not be read as this usually contains the header. You must use an adsorption isotherm only, a desorption swing, or discontinuity in the adsorption from pressure equilibration issues will result in an error, with the PChip interpolation method. 114 | 115 | ## License 116 | 117 | BETSI is distributed under the MIT open source license (see [`LICENSE.txt`](LICENSE.txt)). 118 | 119 | ## Acknowledgements 120 | 121 | Main Developers: James Rampersad, Johannes W. M. Osterrieth, and Nakul Rampal 122 | 123 | This work is supported by: 124 | * [Cambridge International Scholarship](https://www.cambridgetrust.org/) funded by the Cambridge Commonwealth, European & International Trust; 125 | * [Trinity-Henry Barlow Scholarship](https://www.trin.cam.ac.uk/) (Honorary) funded by Trinity College, Cambridge. 126 | 127 | A2ML -------------------------------------------------------------------------------- /betsi/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | """ 4 | from pathlib import Path 5 | 6 | import pandas as pd 7 | import numpy as np 8 | from scipy.interpolate import splrep, PchipInterpolator, pchip_interpolate 9 | 10 | 11 | def get_data(input_file): 12 | """Read pressure and adsorbate uptake data from file. Asserts pressures in units of bar. 13 | 14 | Args: 15 | input_file: Path the path to the input file 16 | 17 | Returns: 18 | Pressure and Quantity adsorbed. 19 | """ 20 | input_file = Path(input_file) 21 | 22 | if str(input_file).find('.txt') != -1 or str(input_file).find('.aif') != -1 or str(input_file).find('.csv') != -1: 23 | pressure, q_adsorbed = get_data_for_txt_aif_csv_two_columns(str(input_file)) 24 | elif str(input_file).find('.XLS') != -1: 25 | pressure, q_adsorbed = get_data_from_micromeritics_xls(str(input_file)) 26 | else: 27 | pressure = np.array([]) 28 | q_adsorbed = np.array([]) 29 | 30 | if len(q_adsorbed) > 0: 31 | start_index = np.argmax(q_adsorbed > 0) 32 | pressure = pressure[start_index:] 33 | q_adsorbed = q_adsorbed[start_index:] 34 | 35 | # else: 36 | # try: 37 | # data = np.loadtxt(str(input_file), skiprows=0, delimiter=',') 38 | # pressure = data[:, 0] 39 | # q_adsorbed = data[:, 1] 40 | # except ValueError: 41 | # data = np.loadtxt(str(input_file), skiprows=1, delimiter=',') 42 | # pressure = data[:, 0] 43 | # q_adsorbed = data[:, 1] 44 | 45 | 46 | comments_to_data = {'has_negative_pressure_points': False,\ 47 | 'monotonically_increasing_pressure': True,\ 48 | 'rel_pressure_between_0_and_1': True} 49 | ## removes negetive relative pressure points if any 50 | negative_pressure_indexes = np.where(pressure < 0)[0] 51 | if len(negative_pressure_indexes) > 0: 52 | comments_to_data['has_negative_pressure_points'] = True 53 | pressure = np.delete(pressure, negative_pressure_indexes) 54 | q_adsorbed = np.delete(q_adsorbed, negative_pressure_indexes) 55 | 56 | ## checks if relative pressure points are monotonically increasing, if not, 57 | ## removes problematic points 58 | if not (pressure == np.sort(pressure)).all(): 59 | comments_to_data['monotonically_increasing_pressure'] = False 60 | temp_index = 0 61 | temp_pressure = pressure 62 | temp_q_adsorbed = q_adsorbed 63 | while temp_index < len(temp_pressure)-1: 64 | if temp_pressure[temp_index+1] <= temp_pressure[temp_index]: 65 | temp_pressure = np.delete(temp_pressure, [temp_index+1]) 66 | temp_q_adsorbed = np.delete(temp_q_adsorbed, [temp_index+1]) 67 | else: 68 | temp_index += 1 69 | pressure = temp_pressure 70 | q_adsorbed = temp_q_adsorbed 71 | 72 | ## checks if relative pressure points lie between 0 and 1 (bar) 73 | if not (pressure < 1.1).all(): 74 | comments_to_data['rel_pressure_between_0_and_1'] = False 75 | pressure_above_one_indexes = np.where(pressure > 1.1)[0] 76 | pressure = np.delete(pressure, pressure_above_one_indexes) 77 | q_adsorbed = np.delete(q_adsorbed, pressure_above_one_indexes) 78 | 79 | comments_to_data['interpolated_points_added'] = False 80 | 81 | ## assert (pressure < 1.1).all(), "Relative pressure must lie between 0 and 1 bar." 82 | return pressure, q_adsorbed, comments_to_data 83 | 84 | 85 | def get_fitted_spline(pressure, q_adsorbed): 86 | """ Fits a cubic spline to the isotherm. 87 | 88 | Args: 89 | pressure: Array of relative pressure values. 90 | q_adsorbed: Array of adsorbate uptake values. 91 | 92 | Returns: 93 | tck tuple of spline parameters. 94 | """ 95 | return splrep(pressure, q_adsorbed, s=50, k=3, quiet=True) 96 | 97 | def get_pchip_interpolation(pressure,q_adsorbed): 98 | """ Fits isotherm with shape preserving pchip interpolation 99 | 100 | Args: 101 | pressure: Array of relative pressure values 102 | q_adsorbed: Array of adsorbate uptake values 103 | 104 | Returns: 105 | Pchip parameters 106 | """ 107 | return PchipInterpolator(pressure,q_adsorbed, axis =0, extrapolate=None) 108 | 109 | def isotherm_pchip_reconstruction(pressure, q_adsorbed, num_of_interpolated_points): 110 | """ Fits isotherm with a pchip interpolation. Can use this to 111 | calculate BET area for difficult isotherms 112 | 113 | Args: pressure: Array of relative pressure values 114 | q_adsorbed: Array of adsorbate uptake values 115 | 116 | Returns: Array of interpolated adsorbate uptake values 117 | 118 | """ 119 | ## x_range = np.linspace(pressure[0], pressure[len(pressure) -1 ], 500) 120 | ## y_pchip = pchip_interpolate(pressure,q_adsorbed,x_range, der=0, axis=0) 121 | 122 | ## pressure = x_range 123 | ## q_adsorbed = y_pchip 124 | 125 | 126 | ## Add new interpolated points using pchip interpolation while having the original data points in the list as well 127 | x_range = np.linspace(np.log10(pressure[0]), np.log10(pressure[len(pressure) -1 ]), num_of_interpolated_points) 128 | delta_x = abs(x_range[1] - x_range[0])/2 129 | for p in np.log10(pressure[1:-1]): 130 | to_be_deleted_indexes = [] 131 | index = np.searchsorted(x_range,p) 132 | if 0 <= index < len(x_range) and abs(p - x_range[index]) < delta_x: 133 | to_be_deleted_indexes.append(index) 134 | if 0 <= (index+1) < len(x_range) and abs(p - x_range[index+1]) < delta_x: 135 | to_be_deleted_indexes.append(index+1) 136 | if 0 <= (index-1) < len(x_range) and abs(p - x_range[index-1]) < delta_x: 137 | to_be_deleted_indexes.append(index-1) 138 | x_range = np.delete(x_range, to_be_deleted_indexes) 139 | x_range = np.append(x_range[1:-1], np.log10(pressure)) 140 | x_range.sort() 141 | x_range = np.power(10, x_range) 142 | 143 | y_pchip = pchip_interpolate(pressure,q_adsorbed,x_range, der=0, axis=0) 144 | 145 | pressure_new = x_range 146 | q_adsorbed_new = y_pchip 147 | 148 | return pressure_new, q_adsorbed_new 149 | 150 | def get_data_for_txt_aif_csv_two_columns(input_file): 151 | """ Read pressure and adsorbate uptake data from file if the file extension is *.txt or *.aif. 152 | this function will be called in the get_data() function, and should not be called alone anywhere in the code 153 | as it might cause some errors. 154 | 155 | Args: 156 | input_file: String of the path to the input file 157 | 158 | Returns: 159 | Pressure and Quantity adsorbed. 160 | """ 161 | 162 | pressure = [] 163 | q_adsorbed = [] 164 | 165 | with open(input_file, 'r') as f: 166 | input_file_lines = f.readlines() 167 | 168 | if len(input_file_lines) == 0: 169 | pressure = np.array(pressure) 170 | q_adsorbed = np.array(q_adsorbed) 171 | return pressure, q_adsorbed 172 | 173 | index_start = 0 174 | index_stop = len(input_file_lines) - 1 175 | index_iter = 0 176 | first_loop_index = 0 177 | 178 | if input_file.find(".aif") != -1: 179 | for line in input_file_lines: 180 | if line.find("loop_") != -1 and first_loop_index == 0: 181 | first_loop_index = index_iter; 182 | if line.find("_adsorp_pressure") != -1: 183 | _adsorp_pressure_index = index_iter; 184 | if line.find("_adsorp_p0") != -1: 185 | _adsorp_p0_index = index_iter; 186 | if line.find("_adsorp_amount") != -1: 187 | index_start = index_iter 188 | if index_start > 0 and (line.find("loop_") != -1 or index_iter + 1 == len(input_file_lines)): 189 | index_stop = index_iter 190 | break 191 | index_iter += 1 192 | for index in range(index_start, index_stop + 1): 193 | val = [] 194 | for t in input_file_lines[index].split(): 195 | try: 196 | val.append(float(t)) 197 | except ValueError: 198 | pass 199 | if len(val) >= 3: 200 | pressure.append(val[_adsorp_pressure_index - first_loop_index - 1]/val[_adsorp_p0_index - first_loop_index - 1]) 201 | q_adsorbed.append(val[index_start - first_loop_index - 1]) 202 | else: 203 | delimiter = " " 204 | if input_file_lines[round(len(input_file_lines)/2)].find(",") != -1: 205 | delimiter = "," 206 | elif input_file_lines[round(len(input_file_lines)/2)].find(" ") != -1: 207 | delimiter = " " 208 | elif input_file_lines[round(len(input_file_lines)/2)].find("\t") != -1: 209 | delimiter = "\t" 210 | for line in input_file_lines: 211 | val = [] 212 | for t in line.split(delimiter): 213 | try: 214 | val.append(float(t)) 215 | except ValueError: 216 | pass 217 | if len(val) >= 2: 218 | pressure.append(val[0]) 219 | q_adsorbed.append(val[1]) 220 | if len(pressure) == 0 or len(q_adsorbed) == 0: 221 | print("You must provide a valid input file!") 222 | 223 | pressure = np.array(pressure) 224 | q_adsorbed = np.array(q_adsorbed) 225 | 226 | return pressure, q_adsorbed 227 | 228 | def get_data_from_micromeritics_xls(input_file): 229 | """ Read pressure and adsorbate uptake data from Micromeritics output files with *.XLS extension 230 | this function will be called in the get_data() function, and should not be called alone anywhere in the code 231 | as it might cause some errors. 232 | 233 | Args: 234 | input_file: String of the path to the input file 235 | 236 | Returns: 237 | Pressure and Quantity adsorbed. 238 | """ 239 | 240 | # pressure = [] 241 | # q_adsorbed = [] 242 | 243 | excel_file = pd.ExcelFile(input_file) 244 | 245 | if len(excel_file.sheet_names) == 1: 246 | excel_df = pd.read_excel(input_file, sheet_name=0) 247 | column_num = 0 248 | for column in excel_df.columns: 249 | if len(excel_df.loc[excel_df[column]=='Isotherm Linear Plot']) > 0: 250 | keyword_column_name = column 251 | keyword_index = excel_df.loc[excel_df[column]=='Isotherm Linear Plot'].index[0] 252 | 253 | adsorption_data_start_index = 0 254 | for row_num in range(keyword_index+1, len(excel_df.index)): 255 | if excel_df[keyword_column_name][row_num] == "Relative Pressure (p/p°)": 256 | adsorption_data_start_index = row_num + 1 257 | 258 | nan_indexes_in_column = np.array(excel_df.loc[excel_df[column].isnull()].index) 259 | adsorption_data_stop_index = nan_indexes_in_column[np.argmax(nan_indexes_in_column > adsorption_data_start_index)] - 1 260 | 261 | if adsorption_data_stop_index < adsorption_data_start_index: 262 | adsorption_data_stop_index = len(excel_df.index) - 1 263 | 264 | if adsorption_data_start_index > 0: 265 | pressure = np.array(excel_df[keyword_column_name][adsorption_data_start_index:adsorption_data_stop_index+1]) 266 | q_adsorbed = np.array(excel_df[excel_df.columns[column_num+1]][adsorption_data_start_index:adsorption_data_stop_index+1]) 267 | else: 268 | pressure = np.array([]) 269 | q_adsorbed = np.array([]) 270 | 271 | break 272 | column_num += 1 273 | 274 | elif len(excel_file.sheet_names) == 0: 275 | pressure = np.array([]) 276 | q_adsorbed = np.array([]) 277 | else: 278 | try: 279 | excel_df = pd.read_excel(input_file, sheet_name="Isotherm Linear Plot") 280 | column_num = 0 281 | for column in excel_df.columns: 282 | if len(excel_df.loc[excel_df[column]=='Isotherm Linear Plot']) > 0: 283 | keyword_column_name = column 284 | keyword_index = excel_df.loc[excel_df[column]=='Isotherm Linear Plot'].index[0] 285 | 286 | adsorption_data_start_index = 0 287 | for row_num in range(keyword_index+1, len(excel_df.index)): 288 | if excel_df[keyword_column_name][row_num] == "Relative Pressure (p/p°)": 289 | adsorption_data_start_index = row_num + 1 290 | 291 | nan_indexes_in_column = np.array(excel_df.loc[excel_df[column].isnull()].index) 292 | adsorption_data_stop_index = nan_indexes_in_column[np.argmax(nan_indexes_in_column > adsorption_data_start_index)] - 1 293 | 294 | if adsorption_data_stop_index < adsorption_data_start_index: 295 | adsorption_data_stop_index = len(excel_df.index) - 1 296 | 297 | if adsorption_data_start_index > 0: 298 | pressure = np.array(excel_df[keyword_column_name][adsorption_data_start_index:adsorption_data_stop_index+1]) 299 | q_adsorbed = np.array(excel_df[excel_df.columns[column_num+1]][adsorption_data_start_index:adsorption_data_stop_index+1]) 300 | else: 301 | pressure = np.array([]) 302 | q_adsorbed = np.array([]) 303 | 304 | break 305 | column_num += 1 306 | 307 | except ValueError: 308 | pressure = np.array([]) 309 | q_adsorbed = np.array([]) 310 | 311 | pressure = np.float64(pressure) 312 | q_adsorbed = np.float64(q_adsorbed) 313 | 314 | return pressure, q_adsorbed -------------------------------------------------------------------------------- /betsi/lib.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Functions for computing the BET areas. 4 | Created on Thu Mar 21 16:24:04 2019 5 | 6 | @author: jwmo2, ls604, jrampersad 7 | """ 8 | import os 9 | from pathlib import Path 10 | 11 | import matplotlib 12 | import numpy as np 13 | from matplotlib import pyplot as plt 14 | from scipy.interpolate import splev 15 | from scipy.optimize import minimize 16 | 17 | from betsi.plotting import create_matrix_plot,regression_diagnostics_plots 18 | from betsi.utils import * 19 | from pprint import pprint 20 | from scipy.interpolate import splrep, pchip_interpolate 21 | matplotlib.use('Qt5Agg') 22 | 23 | ##NITROGEN_RADIUS = 1.62E-19 24 | ##NITROGEN_MOL_VOL = 44.64117195 25 | AVOGADRO_N = 6.02E+23 26 | ## in units of m2 27 | cross_sectional_area = {"N2": 1.62E-19, "Ar": 1.66E-19, "Kr": 2.02E-19, "Xe": 2.32E-19, "CO2": 1.95E-19, "Custom": None} 28 | ## in units of mmol/litre or mol/m3 29 | mol_vol = {"N2": 44.64117195, "Ar": 44.642857, "Kr": 44.642857, "Xe": 44.642857, "CO2": 44.642857, "Custom": None} 30 | 31 | class BETResult: 32 | """ 33 | Structure to hold the unfiltered results obtained from running initial BET analysis on 34 | an isotherm. 35 | """ 36 | 37 | def __init__(self, pressure, q_adsorbed): 38 | 39 | def zero_matrix(dim): 40 | # Helper function to create a square matrix of dimension dim. 41 | return np.zeros([dim, dim]) 42 | 43 | # Pressure & Q_Adsorbed 44 | self.pressure = pressure 45 | self.q_adsorbed = q_adsorbed 46 | 47 | # Apply linearised BET equation 48 | self.linear_y = (pressure / (q_adsorbed * (1. - pressure))) 49 | self.rouq_y = q_adsorbed * (1. - pressure) 50 | 51 | # Fit a line to isotherm. 52 | self.x_range = np.linspace(pressure[0], pressure[len(pressure) -1 ], 10000) 53 | self.fitted_spline = get_fitted_spline(pressure, q_adsorbed) 54 | 55 | # Number of points in each segment: 56 | num_points = len(pressure) 57 | self.point_count = zero_matrix(num_points) 58 | 59 | # Gradients, Intercept and Residual of Linearised BET: 60 | self.fit_grad = zero_matrix(num_points) 61 | self.fit_intercept = zero_matrix(num_points) 62 | self.fit_rsquared = zero_matrix(num_points) 63 | 64 | # 'C' constants and Monolayer loadings: 65 | self.c = zero_matrix(num_points) 66 | self.nm = zero_matrix(num_points) 67 | 68 | # Pressure at the Monolayer loading, error and percentage error: 69 | self.calc_pressure = zero_matrix(num_points) 70 | self.error = zero_matrix(num_points) + 300 71 | self.pc_error = zero_matrix(num_points) + 300 72 | 73 | # Identify the Isotherm Knee 74 | self.knee_index = np.argmax(np.diff(self.rouq_y) < 0) 75 | 76 | # Binary rouquerol `pass` matrices: 77 | self.rouq1 = zero_matrix(num_points) 78 | self.rouq2 = zero_matrix(num_points) 79 | self.rouq3 = zero_matrix(num_points) 80 | self.rouq4 = zero_matrix(num_points) 81 | self.rouq5 = zero_matrix(num_points) 82 | 83 | #Corresponding pressure matrix 84 | self.corresponding_pressure = zero_matrix(num_points) 85 | self.corresponding_pressure_pchip = zero_matrix(num_points) 86 | 87 | # Fill all the matrices 88 | self._compute_betsi_data(pressure, q_adsorbed) 89 | 90 | def _compute_betsi_data(self, pressure, q_adsorbed): 91 | """ Computes Monolayer loadings and applies the rouquerel criteria individually to produce a 92 | dictionary of matrices that can be used in downstream analysis and plotting. 93 | 94 | Args: 95 | pressure: Array of relative pressure values. 96 | q_adsorbed: Array of Nitrogen uptake values. 97 | 98 | Returns: 99 | A Betsi Result Object 100 | """ 101 | 102 | # Fit a line to isotherm. 103 | fitted_spline = get_fitted_spline(pressure, q_adsorbed) 104 | fitted_pchip = get_pchip_interpolation(pressure,q_adsorbed) 105 | 106 | def distance_to_interpolation(_s, _mono): 107 | # Calculate distance of monolayer loading to fitted spline. 108 | return (splev(_s, fitted_spline, der=0, ext=0) - _mono) ** 2 109 | 110 | def distance_to_pchip(_s,_mono): 111 | # Create a BET results object: 112 | 113 | return (fitted_pchip.__call__(_s,nu=0,extrapolate=None) - _mono) ** 2 114 | 115 | 116 | num_points = len(pressure) 117 | 118 | for i in range(num_points): 119 | for j in range(i + 1, num_points + 1): 120 | 121 | # Set the number of points 122 | self.point_count[i, j - 1] = j - i 123 | 124 | # Fit a straight line to points i:j of the linearised equation. Compute C and Nm. 125 | x = np.concatenate( 126 | [np.ones([num_points, 1]), pressure[:, None]], axis=1) 127 | params, residuals, _, _ = np.linalg.lstsq( 128 | x[i:j, :], self.linear_y[i:j], rcond=None) 129 | if residuals: 130 | r2 = 1. - residuals / \ 131 | (self.linear_y[i:j].size * self.linear_y[i:j].var()) 132 | self.fit_rsquared[i, j - 1] = r2 133 | 134 | # Set the linearised BET parameters from the fit. 135 | self.fit_intercept[i, j - 1] = params[0] 136 | self.fit_grad[i, j - 1] = params[1] 137 | self.c[i, j - 1] = self.fit_grad[i, j - 1] / \ 138 | self.fit_intercept[i, j - 1] + 1. 139 | self.nm[i, j - 1] = 1. / \ 140 | (self.fit_grad[i, j - 1] + self.fit_intercept[i, j - 1]) 141 | 142 | for i in range(num_points): 143 | for j in range(i + 1, num_points): 144 | 145 | # ROUQUEROL CRITERIA 1. vol_adsorbed * (1. - pressure) increases monotonically. 146 | deltas = np.diff(self.rouq_y[i:(j + 1)]) 147 | if not (deltas < 0).any(): 148 | self.rouq1[i, j] = 1 149 | deltas_2 = np.diff(self.linear_y[i:(j+1)]) 150 | if (deltas_2 < 0).any(): 151 | self.rouq1[i,j]=0 152 | 153 | # ROUQUEROL CRITERIA 2. Resulting C value must be positive. 154 | if self.c[i, j] <= 0: 155 | continue 156 | self.rouq2[i, j] = 1 157 | 158 | # ROUQUEROL CRITERIA 3. Pressure corresponding to Nm should lie in linear range 159 | self.calc_pressure[i, j] = 1. / (np.sqrt(self.c[i, j]) + 1) 160 | opt_res = minimize(fun=distance_to_interpolation, 161 | x0=self.calc_pressure[i, j], 162 | args=(self.nm[i, j])) 163 | self.corresponding_pressure[i,j] = opt_res.x 164 | opt_res_pchip = minimize(fun=distance_to_pchip,x0=self.calc_pressure[i, j], args=(self.nm[i, j])) 165 | self.corresponding_pressure_pchip[i,j] = opt_res_pchip.x 166 | 167 | 168 | if not pressure[i] < self.corresponding_pressure_pchip[i,j] < pressure[j]: 169 | continue 170 | self.rouq3[i, j] = 1 171 | 172 | # ROUQUEROL CRITERIA 4. Relative Pressure should be *close* to P from BET Theory. 173 | self.error[i, j] = abs( 174 | self.corresponding_pressure_pchip[i,j] - self.calc_pressure[i, j]) 175 | self.pc_error[i, j] = ( 176 | self.error[i, j] / self.corresponding_pressure_pchip[i,j]) * 100. 177 | 178 | ### ROUQUEROL CRITERIA 5. Linear region must end at the knee 179 | ##if j == self.knee_index: 180 | ## self.rouq5[i, j] = 1 181 | self.rouq5[i, j] = 1 182 | 183 | class BETFilterAppliedResults: 184 | """ 185 | Structure obtained from applying a set of custom filters to a BETResult. 186 | 187 | After initialisation with an initialised BETResult object and set of desired features, the 188 | BETFilterAppliedResults object contains all data required to produce any plot. 189 | """ 190 | 191 | def __init__(self, bet_result, **kwargs): 192 | 193 | # Transfer all the properties from the original BET calculation 194 | self.__dict__.update(bet_result.__dict__) 195 | self.filter_params = kwargs 196 | 197 | # Apply the selected filters in turn 198 | filter_mask = np.ones_like(bet_result.c) 199 | 200 | if kwargs.get('use_rouq1', True): 201 | filter_mask = filter_mask * bet_result.rouq1 202 | 203 | if kwargs.get('use_rouq2', True): 204 | filter_mask = filter_mask * bet_result.rouq2 205 | 206 | if kwargs.get('use_rouq3', True): 207 | filter_mask = filter_mask * bet_result.rouq3 208 | 209 | if kwargs.get('use_rouq4', True): 210 | max_perc_error = kwargs.get('max_perc_error', 20) 211 | filter_mask = filter_mask * (bet_result.pc_error < max_perc_error) 212 | 213 | ##if kwargs.get('use_rouq5', False): 214 | ## filter_mask = filter_mask * bet_result.rouq5 215 | if kwargs.get('use_rouq5', True): 216 | filter_mask = filter_mask * bet_result.rouq5 217 | 218 | self.use_rouq5 = kwargs.get('use_rouq5', True) 219 | adsorbate = kwargs.get('adsorbate', "N2") 220 | 221 | if adsorbate == "Custom": 222 | cross_sectional_area[adsorbate] = 1.0E-18 * kwargs.get('cross_sectional_area') 223 | mol_vol[adsorbate] = kwargs.get('molar_volume') 224 | 225 | # Filter results that have less than the minimum points 226 | min_points = kwargs.get('min_num_pts', 10) 227 | filter_mask = filter_mask * (bet_result.point_count >= min_points) 228 | 229 | # Block out results that have less than the minimum R2 230 | min_r2 = kwargs.get('min_r2', 0.9) 231 | filter_mask = filter_mask * (bet_result.fit_rsquared > min_r2) 232 | 233 | ## assert np.sum(filter_mask) != 0, "NO valid areas found" 234 | self.has_valid_areas = False 235 | self.original_pressure_data = bet_result.original_pressure_data 236 | self.original_q_adsorbed_data = bet_result.original_q_adsorbed_data 237 | self.comments_to_data = bet_result.comments_to_data 238 | self.adsorbate = adsorbate 239 | 240 | if np.sum(filter_mask) != 0: 241 | self.has_valid_areas = True 242 | 243 | # Compute valid BET areas 244 | ##self.bet_areas = NITROGEN_RADIUS * AVOGADRO_N * \ 245 | ## NITROGEN_MOL_VOL * bet_result.nm * 0.000001 246 | self.bet_areas = cross_sectional_area[adsorbate] * AVOGADRO_N * \ 247 | mol_vol[adsorbate] * bet_result.nm * 0.000001 248 | self.bet_areas_filtered = self.bet_areas * filter_mask 249 | self.valid_indices = np.where(self.bet_areas_filtered > 0) 250 | 251 | # Define isotherm knee as ending on highest P 252 | self.list = np.where(self.valid_indices[1] == np.amax(self.valid_indices[1])) 253 | self.lower = (self.valid_indices[0])[self.list] 254 | self.upper = (self.valid_indices[1])[self.list] 255 | self.valid_knee_indices = (self.lower, self.upper) 256 | self.knee_only_bet_areas_filtered = self.bet_areas * bet_result.rouq5 257 | 258 | 259 | # Define the valid cases 260 | self.num_valid = len(self.valid_indices[0]) 261 | self.valid_bet_areas = self.bet_areas[self.valid_indices] 262 | self.valid_pc_errors = bet_result.pc_error[self.valid_indices] 263 | self.valid_knee_bet_areas = self.bet_areas[self.valid_knee_indices] 264 | self.valid_knee_pc_errors = bet_result.pc_error[self.valid_knee_indices] 265 | self.valid_calc_pressures = bet_result.calc_pressure[self.valid_indices] 266 | self.valid_nm = bet_result.nm[self.valid_indices] 267 | 268 | 269 | ## was needed in the plotting.py file 270 | self.pc_errors = bet_result.pc_error 271 | 272 | # Find min error and corresponding indices 273 | knee_only_filter = np.zeros([len(bet_result.pressure),len(bet_result.pressure)]) 274 | knee_only_filter[self.valid_knee_indices] = 1 275 | knee_filter = filter_mask * knee_only_filter 276 | filtered_pcerrors = bet_result.pc_error + 1000.0 * (1 - filter_mask) 277 | knee_filtered_pcerrors = bet_result.pc_error + \ 278 | 1000.0 * (1 - knee_filter) 279 | 280 | if self.use_rouq5: 281 | min_i, min_j = np.unravel_index(np.argmin(knee_filtered_pcerrors), filtered_pcerrors.shape) 282 | else: 283 | min_i, min_j = np.unravel_index(np.argmin(filtered_pcerrors), filtered_pcerrors.shape) 284 | self.min_i = min_i 285 | self.min_j = min_j 286 | 287 | self.compute_BET_curve() 288 | 289 | self.std_area = np.std(self.bet_areas[self.valid_indices]) 290 | 291 | def compute_BET_curve(self): 292 | """Function for computing BET curve. This is separated to a different function to allow custom min_i min_j""" 293 | 294 | # Compute BET curve at min point 295 | numerator = (self.nm[self.min_i, self.min_j] * 296 | self.c[self.min_i, self.min_j] * self.x_range) 297 | denominator = (1. - self.x_range) + (1. - self.x_range)\ 298 | * (self.c[self.min_i, self.min_j] - 1.) * self.x_range 299 | 300 | with np.errstate(divide='ignore'): 301 | self.bet_curve = numerator / denominator 302 | 303 | self.min_area = self.bet_areas[self.min_i, self.min_j] 304 | 305 | def find_nearest_idx(self, coords): 306 | """Finds min_i and min_j for the given monlayer error coordinates 307 | """ 308 | # find the index bet areas 309 | betarea_idx = np.abs(self.valid_bet_areas - coords[0]).argmin() 310 | pcerror_idx = np.abs(self.valid_pc_errors - coords[1]).argmin() 311 | 312 | # get the indices 313 | min_i = self.valid_indices[0][betarea_idx] #+ 1 314 | min_j = self.valid_indices[1][pcerror_idx] #+ 1 315 | 316 | self.min_i = min_i 317 | self.min_j = min_j 318 | 319 | self.compute_BET_curve() 320 | 321 | def export(self, filepath): 322 | """ Write all relevant information to the directory at filepath. 323 | 324 | """ 325 | filepath = Path(filepath) 326 | 327 | # Write out the filter settings used to get these results. 328 | with (filepath / 'filter_summary.json').open('w') as fp: 329 | pprint(self.filter_params, fp) 330 | 331 | # Write out the key results. 332 | with (filepath / 'results.txt').open('w') as fp: 333 | print(f"Best area has: ", file=fp) 334 | print(f"Area: {self.min_area} ", file=fp) 335 | print( 336 | f"Total points: {self.point_count[self.min_i, self.min_j]} ", file=fp) 337 | print( 338 | f"R-Squared: {self.fit_rsquared[self.min_i, self.min_j]} ", file=fp) 339 | print( 340 | f"Linear Gradient: {self.fit_grad[self.min_i, self.min_j]} ", file=fp) 341 | print( 342 | f"Intercept: {self.fit_intercept[self.min_i, self.min_j]} ", file=fp) 343 | print( 344 | f"C: {self.c[self.min_i, self.min_j]} ", file=fp) 345 | print( 346 | f"Monolayer Loading: {self.nm[self.min_i, self.min_j]} ", file=fp) 347 | print( 348 | f"Calculated Pressure: {self.calc_pressure[self.min_i, self.min_j]} ", file=fp) 349 | print( 350 | f"Read pressure: {self.corresponding_pressure_pchip[self.min_i,self.min_j]} ", file =fp) 351 | print( 352 | f"Error: {self.error[self.min_i, self.min_j]} ", file=fp) 353 | 354 | # Write out a set of csv files 355 | matrices_f = filepath / 'matrices' 356 | matrices_f.mkdir(exist_ok=True) 357 | np.savetxt(str(matrices_f / 'point_counts.csv'), 358 | self.point_count, delimiter=',', fmt='%i') 359 | np.savetxt(str(matrices_f / 'rouq1.csv'), 360 | self.rouq1, delimiter=',', fmt='%i') 361 | np.savetxt(str(matrices_f / 'rouq2.csv'), 362 | self.rouq2, delimiter=',', fmt='%i') 363 | np.savetxt(str(matrices_f / 'rouq3.csv'), 364 | self.rouq3, delimiter=',', fmt='%i') 365 | np.savetxt(str(matrices_f / 'rouq4.csv'), 366 | self.rouq4, delimiter=',', fmt='%i') 367 | np.savetxt(str(matrices_f / 'rouq5.csv'), 368 | self.rouq5, delimiter=',', fmt='%i') 369 | np.savetxt(str(matrices_f / 'bet_areas_filtered.csv'), 370 | self.bet_areas_filtered, delimiter=',', fmt='%1.3f') 371 | np.savetxt(str(matrices_f / 'bet_areas.csv'), 372 | self.bet_areas, delimiter=',', fmt='%1.3f') 373 | np.savetxt(str(matrices_f / 'fit_rsquared.csv'), 374 | self.fit_rsquared, delimiter=',', fmt='%1.3f') 375 | np.savetxt(str(matrices_f / 'fit_grad.csv'), 376 | self.fit_grad, delimiter=',', fmt='%1.3f') 377 | np.savetxt(str(matrices_f / 'fit_intercept.csv'), 378 | self.fit_intercept, delimiter=',', fmt='%1.3f') 379 | np.savetxt(str(matrices_f / 'c_value.csv'), 380 | self.c, delimiter=',', fmt='%1.3f') 381 | np.savetxt(str(matrices_f / 'nm.csv'), 382 | self.nm, delimiter=',', fmt='%1.3f') 383 | np.savetxt(str(matrices_f / 'calc_pressure.csv'), 384 | self.calc_pressure, delimiter=',', fmt='%1.3f') 385 | np.savetxt(str(matrices_f / 'error.csv'), 386 | self.error, delimiter=',', fmt='%1.3f') 387 | np.savetxt(str(matrices_f / 'pc_error.csv'), 388 | self.pc_error, delimiter=',', fmt='%1.3f') 389 | 390 | 391 | def analyse_file(input_file, output_dir=None, **kwargs): 392 | """ Entry point for performing BET analysis on a single named csv file. 393 | If the output directory does not exist, one is created automatically.""" 394 | 395 | if output_dir is None: 396 | output_dir = Path(os.getcwd() + '/bet_output') 397 | 398 | if isinstance(input_file, str): 399 | input_file = Path(input_file) 400 | 401 | if isinstance(output_dir, str): 402 | output_dir = Path(output_dir) 403 | 404 | output_subdir = output_dir / input_file.name 405 | output_subdir.mkdir(exist_ok=True, parents=True) 406 | 407 | # Compute unfiltered results 408 | pressure, q_adsorbed, comments_to_data = get_data(input_file=input_file) 409 | betsi_unfiltered = BETResult(pressure, q_adsorbed) 410 | 411 | # Apply custom filters: 412 | betsi_filtered = BETFilterAppliedResults(betsi_unfiltered, **kwargs) 413 | 414 | # Export the results 415 | betsi_filtered.export(output_subdir) 416 | 417 | # Create and save a PDF plot 418 | fig = create_matrix_plot(betsi_filtered, name=input_file.stem) 419 | 420 | #fig.tight_layout(pad=0.3, rect=[0, 0, 1, 0.95]) 421 | fig.savefig( 422 | str(output_subdir / f'{input_file.stem}_combined_plot.pdf'), bbox_inches='tight') 423 | #plt.tight_layout() 424 | plt.show() 425 | 426 | # Create and show Diagnostics plot 427 | fig_2 = regression_diagnostics_plots(betsi_filtered,name=input_file.stem) 428 | fig_2.tight_layout(pad=.3, rect=[0,0,1,.95]) 429 | plt.show() 430 | 431 | 432 | if __name__ == "__main__": 433 | analyse_file( 434 | Path(r"/Users/johannesosterrieth/Desktop/q_nu1105.csv")) 435 | -------------------------------------------------------------------------------- /betsi/plotting.py: -------------------------------------------------------------------------------- 1 | """ 2 | Defines a series of plotting functions that can be produced directly from a filtered BET results 3 | object. 4 | 5 | The plots can all be made individually or as part of the larger 2x3 plot matrix. 6 | """ 7 | from matplotlib import pyplot as plt 8 | from matplotlib.ticker import FormatStrFormatter 9 | from scipy.interpolate import splev, pchip_interpolate 10 | import numpy as np 11 | import pandas as pd 12 | import seaborn as sns 13 | import statsmodels.api as sm 14 | from statsmodels.graphics.gofplots import ProbPlot 15 | import matplotlib.gridspec as gridspec 16 | import matplotlib.font_manager 17 | import matplotlib as mpl 18 | 19 | 20 | mpl.rc('font', family='Arial',size=9) 21 | 22 | def regression_diagnostics_plots(bet_filtered, name, fig_2=None): 23 | """ Creates 4 regression diagnostics plots in 2 x 2 matrix 24 | Args: 25 | fit: Matplotlib Figure 26 | bet_filtered : A BETFilterAppliedResults object 27 | name: A string, name to give as a title. 28 | 29 | Returns: 30 | Fig, the updated matplotlib figure 31 | 32 | """ 33 | # Obtaining Regression Diagnostics 34 | 35 | # Gather data and put in DF 36 | min_i = bet_filtered.min_i 37 | min_j = bet_filtered.min_j + 1 38 | p = bet_filtered.pressure 39 | lin_q = bet_filtered.linear_y 40 | P = pd.DataFrame(p) 41 | LIN_Q = pd.DataFrame(lin_q) 42 | dataframe = pd.concat([P, LIN_Q], axis=1) 43 | 44 | # Helper functions 45 | 46 | num_points = len(p) 47 | 48 | def graph(formula, x_range, label=None, ax=None): 49 | """Helper function for plotting cook Distance lines 50 | """ 51 | x = x_range 52 | y = formula(x) 53 | if ax is None: 54 | plt.plot(x, y, label=label, lw=1, ls='--', color='black', alpha = 0.75) 55 | else: 56 | ax.plot(x, y, label=label, lw=1, ls='--', color='black', alpha = 0.75) 57 | 58 | # OLS regression 59 | x = sm.add_constant(p) 60 | model = sm.OLS(lin_q[min_i:min_j], x[min_i:min_j]) 61 | fit = model.fit() 62 | fit_values = fit.fittedvalues 63 | fit_resid = fit.resid 64 | fit_stud_resid = fit.get_influence().resid_studentized_internal 65 | fit_stud_resid_abs_sqrt = np.sqrt(np.abs(fit_stud_resid)) 66 | fit_abs_resid = np.abs(fit_resid) 67 | fit_leverage = fit.get_influence().hat_matrix_diag 68 | fit_CD = fit.get_influence().cooks_distance[0] 69 | 70 | # Make new figure 71 | if fig_2 is None: 72 | fig_2 = plt.figure(constrained_layout=False, figsize=(6.29921, 9.52756)) 73 | mpl.rc('font', family='Arial',size=9) 74 | fig_2.suptitle(f"BETSI Regression Diagnostics for {name}, (Adsorbate: {bet_filtered.adsorbate})\n") 75 | 76 | # "Residual vs fitted" plot 77 | resid_vs_fit = fig_2.add_subplot(2, 2, 1) 78 | sns.residplot(fit_values, fit_resid, data=dataframe, 79 | lowess=True, 80 | scatter_kws={'alpha': .5, 'color': 'red'}, 81 | line_kws={'color': 'black', 'lw': 1, 'alpha': 0.75}, 82 | ax=resid_vs_fit) 83 | resid_vs_fit.axes.set 84 | resid_vs_fit.axes.set_title('Residuals vs Fitted',fontsize=11) 85 | resid_vs_fit.axes.set_xlabel('Fitted Values') 86 | resid_vs_fit.locator_params(axis='x', nbins=4) 87 | resid_vs_fit.axes.set_ylabel('Residuals') 88 | resid_vs_fit.tick_params(axis='both', which='major', labelsize=9) 89 | resid_vs_fit.tick_params(axis='both', which='minor', labelsize=9) 90 | 91 | dfit_values = (max(fit_values) - min(fit_values)) * 1 92 | resid_vs_fit.axes.set_xlim(min(fit_values) - dfit_values, max(fit_values) + dfit_values) 93 | dfit_resid = (max(fit_resid) - min(fit_resid)) * 1 94 | resid_vs_fit.axes.set_ylim(min(fit_resid) - dfit_resid, max(fit_resid) + dfit_resid) 95 | 96 | # "Normal Q-Q" plot 97 | QQ = ProbPlot(fit_stud_resid) 98 | 99 | qq_plot = QQ.qqplot(line='45',markerfacecolor='red',markeredgecolor='red',color='black', alpha=.3, lw=.5, ax=fig_2.add_subplot(2, 2, 2)) 100 | qq_plot.axes[1].set_title('Normal Q-Q') 101 | qq_plot.axes[1].set_xlabel('Theoretical Quantiles') 102 | qq_plot.axes[1].set_ylabel('Studentized Residuals') 103 | qq_plot.axes[1].tick_params(axis='both', which='major') 104 | qq_plot.axes[1].tick_params(axis='both', which='minor') 105 | 106 | abs_norm_resid = np.flip(np.argsort(np.abs(fit_stud_resid)), 0) 107 | abs_norm_resid_top_3 = abs_norm_resid[:3] 108 | for r, i in enumerate(abs_norm_resid_top_3): 109 | # Add annotations 110 | qq_plot.axes[0].annotate(i, xy=(np.flip(QQ.theoretical_quantiles, 0)[r], fit_stud_resid[i]), size = 9) 111 | 112 | # "Scale-location" plot 113 | scale_loc = fig_2.add_subplot(2, 2, 3) 114 | scale_loc.scatter(fit_values, fit_stud_resid_abs_sqrt, alpha=.5, c = 'red') 115 | sns.regplot(fit_values, fit_stud_resid_abs_sqrt, scatter=False, ci=False, lowess=True, line_kws={'color': 'black', 'lw': 1, 'alpha': .75}, ax=scale_loc) 116 | scale_loc.set_title('Scale-Location') 117 | scale_loc.set_xlabel('Fitted Values') 118 | scale_loc.set_ylabel('$\mathregular{\sqrt{|Studentized\ Residuals|}}$') 119 | scale_loc.tick_params(axis='both', which='major', labelsize=9) 120 | scale_loc.tick_params(axis='both', which='minor', labelsize=9) 121 | 122 | abs_sq_norm_resid = np.flip(np.argsort(fit_stud_resid_abs_sqrt), 0) 123 | abs_sq_norm_resid_top_3 = abs_sq_norm_resid[:3] 124 | for i in abs_norm_resid_top_3: 125 | # Add annotations 126 | scale_loc.axes.annotate(i, xy=(fit_values[i], fit_stud_resid_abs_sqrt[i]), size=11) 127 | 128 | scale_loc.axes.set_xlim(min(fit_values) - .2 * max(fit_values), max(fit_values) + .2 * max(fit_values)) 129 | scale_loc.locator_params(axis='x', nbins=4) 130 | 131 | # "Residuals vs leverage" plot 132 | res_vs_lev = fig_2.add_subplot(2, 2, 4) 133 | res_vs_lev.scatter(fit_leverage, fit_stud_resid, alpha=.5, color = 'red') 134 | sns.regplot(fit_leverage, fit_stud_resid, scatter=False, ci=False, lowess=True, line_kws={'color': 'black', 'lw': 1, 'alpha': .75}, ax=res_vs_lev) 135 | res_vs_lev.axes.set_title('Residuals vs Leverage') 136 | res_vs_lev.axes.set_xlabel('Leverage') 137 | res_vs_lev.axes.set_ylabel('Studentized Residuals') 138 | res_vs_lev.tick_params(axis='both', which='major') 139 | res_vs_lev.tick_params(axis='both', which='minor') 140 | 141 | leverage_top_3 = np.flip(np.argsort(fit_CD), 0)[:3] 142 | for i in leverage_top_3: 143 | # Add annotations 144 | res_vs_lev.axes.annotate(i, xy=(fit_leverage[i], fit_stud_resid[i]), size=9) 145 | 146 | p_3 = p[min_i:min_j] 147 | p_2 = len(fit.params) # number of model parameters 148 | graph(lambda p_3: np.sqrt((.5 * p_2 * (1 - p_3)) / p_3), np.linspace(.001, max(fit_leverage), 50), 'Cook\'s Distance', ax=res_vs_lev) # .5 line 149 | graph(lambda p_3: -1 * np.sqrt((.5 * p_2 * (1 - p_3)) / p_3), np.linspace(.001, max(fit_leverage), 50), ax=res_vs_lev) 150 | graph(lambda p_3: np.sqrt((1 * p_2 * (1 - p_3)) / p_3), np.linspace(.001, max(fit_leverage), 50), ax=res_vs_lev) # 1 line 151 | graph(lambda p_3: -1 * np.sqrt((1 * p_2 * (1 - p_3)) / p_3), np.linspace(.001, max(fit_leverage), 50), ax=res_vs_lev) # 1 line 152 | 153 | res_vs_lev.legend(prop={'size': 9}) 154 | 155 | plt.subplots_adjust(bottom=0.07, top=0.91, hspace=.255, wspace=0.315, left=0.12, right=0.92) 156 | 157 | return fig_2 158 | 159 | 160 | def create_matrix_plot(bet_filtered, rouq3, rouq4, name, fig=None): 161 | """ Creates all 6 of the key plots in a 2x3 matrix 162 | 163 | Args: 164 | fig: Matplotlib Figure 165 | bet_filtered: A BETFilterAppliedResults object. 166 | name: A string, name to give as a title. 167 | 168 | Returns: 169 | Fig, the updated matplotlib figure 170 | 171 | """ 172 | # Make Isotherm Plot 173 | if fig is None: 174 | fig = plt.figure(figsize=(6.29921, 9.52756)) 175 | 176 | fig.set_size_inches(6.29921, 9.52756) 177 | fig.suptitle(f"BETSI Analysis for {name}, (Adsorbate: {bet_filtered.adsorbate})\n", fontname="Arial", fontsize = '11') 178 | fig.subplots_adjust(hspace=1.0, top=0.91, bottom=0.07, left=0.052, right=0.865, wspace=0.315) 179 | 180 | gs = gridspec.GridSpec(9, 2, figure=fig) 181 | 182 | # Plot "Adsorption isotherm" 183 | ax = fig.add_subplot(gs[:3,0]) 184 | plot_isotherm(bet_filtered, ax) 185 | 186 | # Plot "Roquerol representation" 187 | ax = fig.add_subplot(gs[:3,1]) 188 | plot_roquerol_representation(bet_filtered, ax) 189 | 190 | # Plot "Linear range" 191 | ax = fig.add_subplot(gs[3:6,0]) 192 | plot_linear_y(bet_filtered, ax) 193 | 194 | # Plot "Filtered BET Areas" 195 | if not rouq3 and not rouq4: 196 | 197 | x_coords = bet_filtered.valid_bet_areas 198 | y_coords = bet_filtered.valid_pc_errors 199 | pc_error_higher_than_290_indexes = np.where(y_coords > 290)[0] 200 | y_coords_lower_than_290 = np.delete(y_coords, pc_error_higher_than_290_indexes) 201 | if not (len(y_coords_lower_than_290) == 0 or len(pc_error_higher_than_290_indexes) == 0): 202 | ax = fig.add_subplot(gs[3:4, 1:]) 203 | ax2 = fig.add_subplot(gs[4:6, 1:]) 204 | # Plots a figure with a break in the y-axis 205 | plot_area_error_1(bet_filtered, ax, ax2) 206 | else: 207 | ax = fig.add_subplot(gs[3:6,1]) 208 | # Plots a figure with NO break in the y-axis 209 | plot_area_error_2(bet_filtered, ax) 210 | else: 211 | ax = fig.add_subplot(gs[3:6,1]) 212 | # Plots a figure with NO break in the y-axis 213 | plot_area_error_2(bet_filtered, ax) 214 | 215 | # Plot "Filtered monolayer-loadings" 216 | ax = fig.add_subplot(gs[6:9,0]) 217 | plot_monolayer_loadings(bet_filtered, ax) 218 | 219 | # Plot "Distribution of Filtered BET Areas" 220 | ax = fig.add_subplot(gs[6:9,1]) 221 | plot_box_and_whisker(bet_filtered, ax) 222 | 223 | plt.tight_layout() 224 | return fig 225 | 226 | 227 | def plot_isotherm(bet_filtered, ax=None): 228 | """ Plot the Isotherm alongside the selected linear region, spline interpolation, point corresponding to lowest error and fit from BET theory. 229 | 230 | Args: 231 | bet_filtered: A BETFilterAppliedResults object. 232 | ax: Optional matplotlib axis object. If none is provided, an axis for a single subplot is made. 233 | 234 | """ 235 | if ax is None: 236 | # When this is not part of a larger plot 237 | fig = plt.figure() 238 | ax = fig.add_subplot(1, 1, 1) 239 | 240 | # Set axis details 241 | mpl.rc('font', family='Arial',size=9) 242 | ax.set_title(f"Adsorption Isotherm", fontname="Arial", fontsize = '11') 243 | ax.set_xlabel(r'$\mathregular{P/P_0}$') 244 | ax.set_ylabel(r'$\mathregular{N_2 uptake}$ (STP) $\mathregular{cm^3 g^{-1}}$') 245 | ax.set_xlim([-0.1, 1.1]) 246 | ax.set_ylim([0.0, max(bet_filtered.q_adsorbed)]) 247 | ax.tick_params(axis='both', which='major', labelsize=9) 248 | ax.tick_params(axis='both', which='minor', labelsize=9) 249 | 250 | # Get min_i, min_j from the filtered result 251 | min_i = bet_filtered.min_i 252 | min_j = bet_filtered.min_j 253 | 254 | # Plot the isotherm itself 255 | ax.scatter(bet_filtered.pressure[:min_i], bet_filtered.q_adsorbed[:min_i], color='black', edgecolors='black', label='Adsorption Isotherm', alpha=0.50) 256 | ax.scatter(bet_filtered.pressure[min_j+1:], bet_filtered.q_adsorbed[min_j+1:], color='black', edgecolors='black',alpha = 0.50) 257 | 258 | # Plot the part corresponding to the selected linear region 259 | ax.scatter(bet_filtered.pressure[min_i:min_j + 1], bet_filtered.q_adsorbed[min_i:min_j + 1], marker='s',color='red', edgecolors='red', label='Linear Range', alpha=0.5) 260 | 261 | # plot pchip interpolation 262 | ax.plot(bet_filtered.x_range, pchip_interpolate(bet_filtered.pressure, bet_filtered.q_adsorbed, bet_filtered.x_range), color='black', alpha=.75, label='Pchip Interpolation') 263 | # plot corresponding pressure 264 | ax.scatter(bet_filtered.corresponding_pressure_pchip[min_i, min_j], bet_filtered.nm[min_i, min_j], marker='^', color='blue', edgecolor='blue', label='$\mathregular{N_m}$ Read', alpha=0.50) 265 | 266 | # Plot selected Monolayer loading (single point) 267 | ax.scatter(bet_filtered.calc_pressure[min_i, min_j], bet_filtered.nm[min_i, min_j], marker='v', color='green', edgecolors='green', edgecolor='green', label='$\mathregular{N_m}$ BET') 268 | 269 | # Plot the BET curve derived from BET theory 270 | ax.plot(bet_filtered.x_range, bet_filtered.bet_curve, c='g', label='BET Fit', alpha=.5) 271 | 272 | # Add a legend 273 | ax.autoscale(False) 274 | ax.legend(prop={'size': 9}) 275 | 276 | 277 | def plot_roquerol_representation(bet_filtered, ax=None): 278 | """ Plot the Roquerol representation with points corresponding to those in the selected linear region highlighted. 279 | 280 | Args: 281 | bet_filtered: A BETFilterAppliedResults object. 282 | ax: Optional matplotlib axis object. If none is provided, an axis for a single subplot is made. 283 | 284 | """ 285 | 286 | if ax is None: 287 | # When this is not part of a larger plot 288 | fig = plt.figure() 289 | ax = fig.add_subplot(1, 1, 1) 290 | 291 | # Set axis details 292 | mpl.rc('font', family='Arial',size=9) 293 | ax.set_title(f"Rouquerol Representation", fontname="Arial", fontsize = '11') 294 | ax.set_xlabel(r'$\mathregular{P/P_0}$') 295 | ax.set_ylabel(r'$\mathregular{N(1-P/P_0)}$', fontname="Arial") 296 | ax.tick_params(axis='both', which='major', labelsize=9) 297 | ax.tick_params(axis='both', which='minor', labelsize=9) 298 | 299 | # Get min_i, min_j from the filtered result 300 | min_i = bet_filtered.min_i 301 | min_j = bet_filtered.min_j 302 | 303 | # Plot the main Roquerol representation scatter 304 | ax.scatter(bet_filtered.pressure[:min_i], bet_filtered.rouq_y[:min_i], edgecolors='black', color='black', label=r'$\mathregular{N(1-P/P_0)}$', alpha=0.5) 305 | ax.scatter(bet_filtered.pressure[min_j+1:], bet_filtered.rouq_y[min_j+1:], edgecolors='black', color='black', alpha=0.5) 306 | 307 | # Plot the part corresponding to the linear region 308 | ax.scatter(bet_filtered.pressure[min_i:min_j + 1], bet_filtered.rouq_y[min_i:min_j + 1], marker='s', color='red', edgecolors='none', label='Linear Range', alpha=0.50) 309 | 310 | # Add a legend 311 | ax.legend(prop={'size': 9}) 312 | 313 | 314 | def plot_linear_y(bet_filtered, ax=None): 315 | """ Plot the selected linear region of the linearised BET equation and print the formula of the 316 | straight line alongside it. 317 | 318 | Args: 319 | bet_filtered: A BETFilterAppliedResults object. 320 | ax: Optional matplotlib axis object. If none is provided, an axis for a single subplot is made. 321 | 322 | """ 323 | if ax is None: 324 | # When this is not part of a larger plot: 325 | fig = plt.figure() 326 | ax = fig.add_subplot(1, 1, 1) 327 | 328 | # Set axis details 329 | mpl.rc('font', family='Arial',size=9) 330 | ax.set_title(f"Linear Range", fontname="Arial",fontsize='11') 331 | ax.set_xlabel(r'$\mathregular{P/P_0}$') 332 | ax.set_ylabel(r'$\mathregular{P/N(P_0 - P)}$', fontname="Arial", fontsize = '9') 333 | ax.tick_params(axis='both', which='major', labelsize=9) 334 | ax.tick_params(axis='both', which='minor', labelsize=9) 335 | 336 | # Get min_i, min_j from the filtered result 337 | min_i = bet_filtered.min_i 338 | min_j = bet_filtered.min_j 339 | 340 | ### Plot the points in the selected linear region 341 | ##ax.scatter(bet_filtered.pressure[min_i:min_j + 1], bet_filtered.linear_y[min_i:min_j + 1], marker='s', color='r', edgecolors='red', label='Interpolated Points', alpha = 0.50) 342 | 343 | ## Detect data points that were in the original adsorption data 344 | if bet_filtered.comments_to_data['interpolated_points_added']: 345 | # Plot the points in the selected linear region 346 | ax.scatter(bet_filtered.pressure[min_i:min_j + 1], bet_filtered.linear_y[min_i:min_j + 1], marker='s', color='green', edgecolors='green', label='Interpolated Points', alpha = 0.50) 347 | 348 | ###original_pressure_points_used = [x for x in bet_filtered.pressure[min_i:min_j + 1] if x in bet_filtered.original_pressure_data] 349 | original_pressure_points_used = [x for x in bet_filtered.original_pressure_data if bet_filtered.pressure[min_i] <= x <= bet_filtered.pressure[min_j]] 350 | original_points_indexes = [] 351 | for x in original_pressure_points_used: 352 | ##original_points_indexes.append(np.where(bet_filtered.pressure[min_i:min_j + 1] == x)[0][0]) 353 | original_points_indexes.append(np.where(abs((bet_filtered.pressure[min_i:min_j + 1]-x)/x) * 100 < 0.01)[0][0]) 354 | original_linear_y_points_used = bet_filtered.linear_y[min_i:min_j + 1][original_points_indexes] 355 | if len(original_pressure_points_used) != 0: 356 | ax.scatter(original_pressure_points_used, original_linear_y_points_used, marker='s', color='red', edgecolors='red', label='Original Points', alpha = 0.50) 357 | # Add a legend 358 | ax.autoscale(False) 359 | ax.legend(loc=4, prop={'size': 9}) 360 | else: 361 | # Plot the points in the selected linear region 362 | ax.scatter(bet_filtered.pressure[min_i:min_j + 1], bet_filtered.linear_y[min_i:min_j + 1], marker='s', color='r', edgecolors='red', label='Interpolated Points', alpha = 0.50) 363 | 364 | 365 | 366 | # Plot the straight line obtained from the linear regression 367 | largest_valid_x = max(bet_filtered.pressure[min_i:min_j + 1]) 368 | intercept_at_opt = bet_filtered.fit_intercept[min_i, min_j] 369 | grad_at_opt = bet_filtered.fit_grad[min_i, min_j] 370 | highest_valid_pressure = max(bet_filtered.pressure[min_i:min_j + 1]) 371 | end_y = grad_at_opt * highest_valid_pressure + intercept_at_opt 372 | 373 | ax.plot([0, largest_valid_x], [intercept_at_opt, end_y], color='black',alpha=.75) 374 | 375 | # Set the plot limits 376 | smallest_y = 0.1 * min(bet_filtered.linear_y[min_i:min_j + 1]) 377 | biggest_y = 1.1 * max(bet_filtered.linear_y[min_i:min_j + 1]) 378 | smallest_x = 0.1 * min(bet_filtered.pressure[min_i:min_j + 1]) 379 | biggest_x = 1.1 * max(bet_filtered.pressure[min_i:min_j + 1]) 380 | 381 | ax.set_ylim(smallest_y, biggest_y) 382 | ax.set_xlim(smallest_x, biggest_x) 383 | 384 | # Print the equation of the straight line. 385 | ax.yaxis.set_major_formatter(FormatStrFormatter('%.1E')) 386 | ##y_eqn = r"y = {0:.8f}$x$ + {1:.8f}".format(bet_filtered.fit_grad[min_i, min_j], bet_filtered.fit_intercept[min_i, min_j]) 387 | y_eqn = r"y = {0:0.4e}$x$ + {1:0.4e}".format(bet_filtered.fit_grad[min_i, min_j], bet_filtered.fit_intercept[min_i, min_j]) 388 | r_eqn = r"$R^2$ = {0:.8f}".format(bet_filtered.fit_rsquared[min_i, min_j]) 389 | ax.text(0.05, 0.9, y_eqn, {'color': 'black', 'fontsize': 9}, transform=ax.transAxes) 390 | ax.text(0.05, 0.825, r_eqn, {'color': 'black', 'fontsize': 9}, transform=ax.transAxes) 391 | 392 | 393 | def plot_area_error_1(bet_filtered, ax=None, ax2=None): 394 | """ Plot the distribution of valid BET areas, highlight those ending on the `knee` and print 395 | start and end indices. 396 | 397 | Args: 398 | bet_filtered: A BETFilterAppliedResults object. 399 | ax: Optional matplotlib axis object. If none is provided, an axis for a single subplot is made. 400 | 401 | """ 402 | 403 | min_i = bet_filtered.min_i 404 | min_j = bet_filtered.min_j 405 | 406 | if ax is None: 407 | # When this is not part of a larger plot 408 | fig = plt.figure() 409 | ax = fig.add_subplot(1, 1, 1) 410 | 411 | # Set axis details 412 | mpl.rc('font', family='Arial',size=9) 413 | ax.set_title('Filtered BET Areas ', fontname="Arial") 414 | ax2.set_xlabel(r'BET Area $\mathregular{m^2 g^{-1}}$', fontname="Arial", fontsize = '9') 415 | ax2.set_ylabel(r'Percentage Error %', fontname="Arial", fontsize = '9') 416 | ax2.yaxis.set_label_coords(-0.1, 0.78) 417 | ax.tick_params(axis='both', which='major', labelsize=9) 418 | ax.tick_params(axis='both', which='minor', labelsize=9) 419 | ax2.tick_params(axis='both', which='major', labelsize=9) 420 | ax2.tick_params(axis='both', which='minor', labelsize=9) 421 | 422 | x_coords = bet_filtered.valid_bet_areas 423 | y_coords = bet_filtered.valid_pc_errors 424 | 425 | pc_error_higher_than_290_indexes = np.where(y_coords > 290)[0] 426 | y_coords_lower_than_290 = np.delete(y_coords, pc_error_higher_than_290_indexes) 427 | 428 | ##x_coords_nonvalid = np.array([x for x in x_coords if x not in bet_filtered.valid_knee_bet_areas]) 429 | ##y_coords_nonvalid = np.array([y for y in y_coords if y not in bet_filtered.valid_knee_pc_errors]) 430 | 431 | ## Find indices that are in valid_indices but not in valid_knee_indices 432 | temp_x = [] 433 | temp_y = [] 434 | for i in range(len(bet_filtered.valid_indices[0])): 435 | a = [bet_filtered.valid_indices[0][i], bet_filtered.valid_indices[1][i]] 436 | temp_checker = 0 437 | for j in range(len(bet_filtered.valid_knee_indices[0])): 438 | b = [bet_filtered.valid_knee_indices[0][j], bet_filtered.valid_knee_indices[1][j]] 439 | if a==b: 440 | temp_checker = 1 441 | if temp_checker == 0: 442 | temp_x.append(a[0]) 443 | temp_y.append(a[1]) 444 | bet_filtered.valid_indeces_but_not_knee = np.array([temp_x,temp_y]) 445 | bet_filtered.valid_indeces_but_not_knee = tuple(bet_filtered.valid_indeces_but_not_knee) 446 | 447 | x_coords_nonvalid = bet_filtered.bet_areas[bet_filtered.valid_indeces_but_not_knee] 448 | y_coords_nonvalid = bet_filtered.pc_error[bet_filtered.valid_indeces_but_not_knee] 449 | 450 | 451 | # Scatter plot of the Error across valid areas 452 | 453 | ax.scatter(x_coords_nonvalid, y_coords_nonvalid, color='red', edgecolors='red', picker=5, alpha =0.5) 454 | ax.scatter(bet_filtered.valid_knee_bet_areas, bet_filtered.valid_knee_pc_errors, color='b', edgecolors='b', marker='s', picker=5, alpha=0.5) 455 | ax.scatter(bet_filtered.bet_areas[min_i, min_j], bet_filtered.pc_error[min_i, min_j], marker='s', color='yellow', edgecolors='yellow') 456 | 457 | ax2.scatter(x_coords_nonvalid, y_coords_nonvalid, color='r', edgecolors='r', picker=5, alpha = 0.5) 458 | ax2.scatter(bet_filtered.valid_knee_bet_areas, bet_filtered.valid_knee_pc_errors, color='b', edgecolors='b', marker='s', picker=5, alpha=.5) 459 | ax2.scatter(bet_filtered.bet_areas[min_i, min_j], bet_filtered.pc_error[min_i, min_j], marker='s', color='orange', edgecolors='orange') 460 | 461 | # Axis settings 462 | ax.set_ylim(290, 310) 463 | ## define the y-axis upper limit of the lower figure (ax2) 464 | ax2.set_ylim(top=max(y_coords_lower_than_290)*1.1) 465 | 466 | ax.spines['bottom'].set_visible(False) 467 | ax2.spines['top'].set_visible(False) 468 | ax.xaxis.set_visible(False) 469 | ax.tick_params(labeltop='off') 470 | ax2.tick_params(labeltop='off') 471 | ax2.xaxis.tick_bottom() 472 | 473 | d = .015 # how big to make the diagonal lines in axes coordinates 474 | # arguments to pass plot, just so we don't keep repeating them 475 | kwargs = dict(transform=ax.transAxes, color='k', clip_on=False) 476 | ax.plot((-d, +d), (-d, +d), **kwargs) # top-left diagonal 477 | ax.plot((1 - d, 1 + d), (-d, +d), **kwargs) # top-right diagonal 478 | 479 | kwargs.update(transform=ax2.transAxes) # switch to the bottom axes 480 | ax2.plot((-d, +d), (1 - d, 1 + d), **kwargs) # bottom-left diagonal 481 | ax2.plot((1 - d, 1 + d), (1 - d, 1 + d), **kwargs) # bottom-right diagonal 482 | 483 | # Add text annotation to each error point 484 | for i, type in enumerate(x_coords): 485 | index = f"({bet_filtered.valid_indices[0][i] + 1}," \ 486 | f" {bet_filtered.valid_indices[1][i] + 1})" 487 | plt.text(x_coords[i], y_coords[i], index, fontsize=7, clip_on=True) 488 | 489 | 490 | def plot_area_error_2(bet_filtered, ax=None): 491 | """ Plot the distribution of valid BET areas, highlight those ending on the `knee` and print start and end indices. 492 | 493 | Args: 494 | bet_filtered: A BETFilterAppliedResults object. 495 | ax: Optional matplotlib axis object. If none is provided, an axis for a single subplot is made. 496 | 497 | """ 498 | min_i = bet_filtered.min_i 499 | min_j = bet_filtered.min_j 500 | 501 | if ax is None: 502 | # When this is not part of a larger plot 503 | fig = plt.figure() 504 | ax = fig.add_subplot(1, 1, 1) 505 | 506 | # Set axis details 507 | ax.set_title('Filtered BET Areas ', fontname="Arial") 508 | ax.set_xlabel(r'BET Area $\mathregular{m^2 g^{-1}}$', fontname="Arial", fontsize = '9') 509 | ax.set_ylabel(r'Percentage Error %', fontname="Arial", fontsize = '9') 510 | ax.tick_params(axis='both', which='major', labelsize=9) 511 | ax.tick_params(axis='both', which='major', labelsize=9) 512 | 513 | x_coords = bet_filtered.valid_bet_areas 514 | y_coords = bet_filtered.valid_pc_errors 515 | ##x_coords_nonvalid = np.array([x for x in x_coords if x not in bet_filtered.valid_knee_bet_areas]) 516 | ##y_coords_nonvalid = np.array([y for y in y_coords if y not in bet_filtered.valid_knee_pc_errors]) 517 | 518 | ## Find indices that are in valid_indices but not in valid_knee_indices 519 | temp_x = [] 520 | temp_y = [] 521 | for i in range(len(bet_filtered.valid_indices[0])): 522 | a = [bet_filtered.valid_indices[0][i], bet_filtered.valid_indices[1][i]] 523 | temp_checker = 0 524 | for j in range(len(bet_filtered.valid_knee_indices[0])): 525 | b = [bet_filtered.valid_knee_indices[0][j], bet_filtered.valid_knee_indices[1][j]] 526 | if a==b: 527 | temp_checker = 1 528 | if temp_checker == 0: 529 | temp_x.append(a[0]) 530 | temp_y.append(a[1]) 531 | bet_filtered.valid_indeces_but_not_knee = np.array([temp_x,temp_y]) 532 | bet_filtered.valid_indeces_but_not_knee = tuple(bet_filtered.valid_indeces_but_not_knee) 533 | if len(bet_filtered.valid_indeces_but_not_knee[0]) > 0: 534 | x_coords_nonvalid = bet_filtered.bet_areas[bet_filtered.valid_indeces_but_not_knee] 535 | y_coords_nonvalid = bet_filtered.pc_error[bet_filtered.valid_indeces_but_not_knee] 536 | else: 537 | x_coords_nonvalid = np.empty(0) 538 | y_coords_nonvalid = np.empty(0) 539 | 540 | 541 | # Scatter plot of the Error across valid areas 542 | ax.scatter(x_coords_nonvalid, y_coords_nonvalid, color='red', edgecolors='red', picker=5, alpha=0.5) 543 | ax.scatter(bet_filtered.valid_knee_bet_areas, bet_filtered.valid_knee_pc_errors, color='b', edgecolors='b', marker='s', picker=5, alpha=0.50) 544 | ax.scatter(bet_filtered.bet_areas[min_i, min_j], bet_filtered.pc_error[min_i, min_j], marker='s', color='yellow', edgecolors='yellow') 545 | 546 | # Add text annotation to each error point. 547 | for i, type in enumerate(x_coords): 548 | index = f"({bet_filtered.valid_indices[0][i] + 1}," \ 549 | f" {bet_filtered.valid_indices[1][i] + 1})" 550 | plt.text(x_coords[i], y_coords[i], index, fontsize=7, clip_on=True) 551 | 552 | # Set the Y-limit on the errors 553 | ax.set_ylim(0, max(y_coords) * 1.1) 554 | 555 | def plot_monolayer_loadings(bet_filtered, ax=None): 556 | """ Plot the distribution of monolayer loadings alonside the Isotherm and fitted spline. 557 | 558 | Args: 559 | bet_filtered: A BETFilterAppliedResults object. 560 | ax: Optional matplotlib axis object. If none is provided, an axis for a single subplot is made. 561 | 562 | """ 563 | 564 | if ax is None: 565 | # When this is not part of a larger plot 566 | fig = plt.figure() 567 | ax = fig.add_subplot(1, 1, 1) 568 | 569 | # Set axis details 570 | mpl.rc('font', family='Arial',size=9) 571 | ax.set_title("Filtered Monolayer-Loadings", fontname="Arial") 572 | ax.set_xlabel(r'$\mathregular{P/P_0}$', fontname="Arial", fontsize = '9') 573 | ax.set_ylabel(r'$\mathregular{N_2 uptake}$ (STP) $\mathregular{cm^3 g^{-1}}$') 574 | ax.tick_params(axis='both', which='major', labelsize=9) 575 | ax.tick_params(axis='both', which='minor', labelsize=9) 576 | 577 | # Get min_i, min_j from the filtered result 578 | min_i = bet_filtered.min_i 579 | min_j = bet_filtered.min_j 580 | 581 | # Plot the Isotherm itself 582 | ax.scatter(bet_filtered.pressure[:min_i], bet_filtered.q_adsorbed[:min_i], color='black', edgecolors='black', alpha=0.5) 583 | ax.scatter(bet_filtered.pressure[min_j+1:], bet_filtered.q_adsorbed[min_j+1:], color='black', edgecolors='black', label='Adsorption Isotherm', alpha=0.5) 584 | # Plot the fitted spline 585 | ax.plot(bet_filtered.x_range, pchip_interpolate(bet_filtered.pressure, bet_filtered.q_adsorbed, bet_filtered.x_range), color='black',alpha=.5, label='Pchip Interpolation') 586 | 587 | # Plot the valid monolayer loadings. 588 | ax.scatter(bet_filtered.valid_calc_pressures, bet_filtered.valid_nm, marker='^', color='blue', edgecolors='blue', label='$\mathregular{N_m}$ valid', alpha=0.5) 589 | 590 | # Plot the valid optimum linear range 591 | ax.scatter(bet_filtered.pressure[min_i:min_j + 1], bet_filtered.q_adsorbed[min_i:min_j + 1], marker='s', color='red', edgecolors='red', label='Linear Range', alpha=0.5) 592 | 593 | # Plot the single optimum Monolayer loading 594 | ax.scatter(bet_filtered.calc_pressure[min_i, min_j], bet_filtered.nm[min_i, min_j], marker='s', color='orange', edgecolors='orange', label='N$_m$ BET') 595 | 596 | # Plot the Fit obtained from the BET equation 597 | ax.plot(bet_filtered.x_range, bet_filtered.bet_curve, c='g',alpha=.5, label='BET Fit') 598 | 599 | # Set the Xlimits and add a legend 600 | ax.set_xlim([-0.001, max(bet_filtered.valid_calc_pressures)]) 601 | ax.set_ylim([0.0, max(bet_filtered.q_adsorbed)]) 602 | 603 | # Add a legend 604 | ax.autoscale(False) 605 | ax.legend(loc=4, prop={'size': 9}) 606 | 607 | 608 | def plot_box_and_whisker(bet_filtered, ax=None): 609 | """ Plot a box and whisker plot for the valid BET areas. 610 | 611 | Args: 612 | bet_filtered: A BETFilterAppliedResults object. 613 | ax: Optional matplotlib axis object. If none is provided, an axis for a single subplot is made. 614 | 615 | """ 616 | if ax is None: 617 | # When this is not part of a larger plot: 618 | fig = plt.figure() 619 | ax = fig.add_subplot(1, 1, 1) 620 | 621 | # Set axis details. 622 | mpl.rc('font', family='Arial',size=9) 623 | ax.set_title('Distribution of Filtered BET Areas', fontname="Arial") 624 | ax.set_ylabel(r'BET Area $\mathregular{m^2 g^{-1}}$', fontname="Arial", fontsize = '9') 625 | ax.tick_params(axis='both', which='major', labelsize=9) 626 | ax.tick_params(axis='both', which='minor', labelsize=9) 627 | 628 | min_i = bet_filtered.min_i 629 | min_j = bet_filtered.min_j 630 | 631 | y_min = [bet_filtered.bet_areas[min_i, min_j]] 632 | x_min = np.random.normal(1, 0.04, 1) 633 | 634 | y = list(set(bet_filtered.valid_knee_bet_areas) - set(y_min)) 635 | x = np.random.normal(1, 0.04, size=len(y)) 636 | 637 | y_2 = list(set(bet_filtered.valid_bet_areas) - set(y) - set(y_min)) 638 | x_2 = np.random.normal(1, 0.04, size=len(y_2)) 639 | 640 | # Plot the filtered BET areas 641 | ax.scatter(x_2, y_2, alpha=0.5, color='red', edgecolor='red') 642 | ax.scatter(x, y, color='blue', edgecolor='blue', alpha=0.5) 643 | ax.scatter(x_min, y_min, marker='s', color='orange', edgecolor='orange') 644 | 645 | if len(x)==0: 646 | ax.set_xlim([.75,1.25]) 647 | dy = y_min[0]*0.25 648 | ax.set_ylim(y_min[0] - dy, y_min[0] + dy) 649 | else: 650 | ax.set_xlim([.75, 1.25]) 651 | dy = (max(bet_filtered.valid_bet_areas)-min(bet_filtered.valid_bet_areas)) * 1 652 | ax.set_ylim(min(bet_filtered.valid_bet_areas) - dy, max(bet_filtered.valid_bet_areas) + dy) 653 | # if len(y_2)==0: 654 | # dy = (max(y)-min(y)) * 1 655 | # ax.set_ylim(min(y) - dy, max(y) + dy) 656 | # else: 657 | # dy = (max(y_2) - min(y_2)) * 1 658 | # ax.set_ylim(min(y_2) - dy, max(y_2) + dy) 659 | 660 | # Make the boxplot of valid areas 661 | medianprops = dict(linestyle='--', linewidth=1, color='black', alpha=0.35) # median line properties 662 | ax.boxplot(bet_filtered.valid_bet_areas, showfliers=False, medianprops=medianprops) 663 | ax.set_xticks([]) 664 | 665 | # Write BET area 666 | called_BET_area = """BET Area = {0:0.0f} $m^2/g$""".format(np.around((bet_filtered.bet_areas[min_i, min_j])), decimals=0, out=None) 667 | ax.text(0.05, 0.90, called_BET_area, {'color': 'black', 'fontsize': 9}, transform=ax.transAxes) 668 | 669 | points_range = f"Selected Points Range: ({min_i+1},{min_j+1})" 670 | ax.text(0.05, 0.80, points_range, {'color': 'black', 'fontsize': 9}, transform=ax.transAxes) 671 | -------------------------------------------------------------------------------- /betsi/gui.py: -------------------------------------------------------------------------------- 1 | # basics 2 | import os 3 | 4 | from PyQt5 import QtCore 5 | from PyQt5 import QtGui 6 | # qt gui framework 7 | from PyQt5.QtWidgets import * 8 | # matplotlib for plotting 9 | import matplotlib.pyplot as plt 10 | from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas 11 | from matplotlib.figure import Figure 12 | import logging 13 | import sys 14 | 15 | import traceback 16 | 17 | # core lib 18 | plt.ion() 19 | from betsi.lib import * 20 | from betsi.plotting import * 21 | 22 | 23 | class BETSI_gui(QMainWindow): 24 | """Defines the main window of the BETSI gui and adds all the toolbars. 25 | The main widget is the BETSI_widget""" 26 | 27 | def __init__(self): 28 | super().__init__() 29 | 30 | self.title = 'BETSI' 31 | self.setWindowTitle(self.title) 32 | 33 | # define the menu bar 34 | menubar = self.menuBar() 35 | fileMenu = menubar.addMenu('&File') 36 | toolsMenu = menubar.addMenu('&Tools') 37 | 38 | # option for importing a single file. 39 | importfile_menu = fileMenu.addAction('&Import File') 40 | importfile_menu.setShortcut("Ctrl+I") 41 | importfile_menu.triggered.connect(self.import_file) 42 | 43 | # option for changing the root output directory. 44 | outputdir_menu = fileMenu.addAction('&Set Output Directory') 45 | outputdir_menu.setShortcut("Ctrl+O") 46 | outputdir_menu.triggered.connect(self.set_output_dir) 47 | 48 | # option for running on a chosen directory. 49 | directoryrun_menu = fileMenu.addAction('&Analyse Directory') 50 | directoryrun_menu.setShortcut("Ctrl+D") 51 | directoryrun_menu.triggered.connect(self.analyse_dir) 52 | 53 | # option for clearing the memory and resetting to defaults 54 | clear_menu = toolsMenu.addAction('&Clear') 55 | clear_menu.setShortcut("Ctrl+C") 56 | clear_menu.triggered.connect(self.clear) 57 | 58 | # option for replotting 59 | replot_menu = toolsMenu.addAction('&Replot') 60 | replot_menu.triggered.connect(self.replot_betsi) 61 | 62 | self.betsimagic_menu_action = QAction( 63 | 'BETSI Magic', self, checkable=True) 64 | self.betsimagic_menu_action.setStatusTip( 65 | "Resets the settings to default values and performs BETSI calculation") 66 | self.betsimagic_menu_action.setChecked(False) 67 | self.betsimagic_menu_action.triggered.connect(self.trigger_betsimagic) 68 | 69 | betsimagic_menu = toolsMenu.addAction(self.betsimagic_menu_action) 70 | 71 | # define the widget 72 | self.betsi_widget = BETSI_widget(self) 73 | self.setCentralWidget(self.betsi_widget) 74 | 75 | # enable drag and drop 76 | self.setAcceptDrops(True) 77 | 78 | def set_output_dir(self): 79 | self.betsi_widget.set_output_dir() 80 | 81 | def analyse_dir(self): 82 | dir_path = QFileDialog.getExistingDirectory( 83 | self, 'Select Directory', os.getcwd()) 84 | print(f'Imported directory: {Path(dir_path).name}') 85 | self.betsi_widget.analyse_directory(dir_path) 86 | 87 | 88 | def import_file(self): 89 | ## file_path = QFileDialog.getOpenFileName( 90 | ## self, 'Select File', os.getcwd(), '*.csv')[0] 91 | file_path = QFileDialog.getOpenFileName( 92 | self, 'Select File', os.getcwd(), "*.csv *.aif *.txt *.XLS")[0] 93 | self.betsi_widget.target_filepath = file_path 94 | self.betsi_widget.populate_table(csv_path=file_path) 95 | print(f'Imported file: {Path(file_path).name}') 96 | 97 | def dragEnterEvent(self, e): 98 | data = e.mimeData() 99 | urls = data.urls() 100 | drag_type = [u.scheme() for u in urls] 101 | paths = [u.toLocalFile() for u in urls] 102 | extensions = [os.path.splitext(p)[-1] for p in paths] 103 | # accept files only for now. 104 | 105 | ##if all(dt == 'file' for dt in drag_type) and all(ext == '.csv' for ext in extensions): 106 | if all(dt == 'file' for dt in drag_type) and all(ext in ['.csv', '.aif', '.txt', '.XLS'] for ext in extensions): 107 | e.accept() 108 | elif len(drag_type) == 1 and os.path.isdir(paths[0]): 109 | e.ignore() 110 | else: 111 | e.ignore() 112 | 113 | def dropEvent(self, e): 114 | data = e.mimeData() 115 | urls = data.urls() 116 | paths = [u.toLocalFile() for u in urls] 117 | 118 | # Single path to csv file 119 | 120 | ##if len(paths) == 1 and Path(paths[0]).suffix == '.csv': 121 | if len(paths) == 1 and Path(paths[0]).suffix in ['.csv', '.aif', '.txt', '.XLS']: 122 | self.betsi_widget.target_filepath = paths[0] 123 | self.betsi_widget.populate_table(csv_path=paths[0]) 124 | print(f'Imported file: {Path(paths[0]).name}') 125 | 126 | self.betsi_widget.bet_object = None 127 | self.betsi_widget.bet_filter_result = None 128 | 129 | self.betsi_widget.run_calculation() 130 | 131 | def clear(self): 132 | self.betsi_widget.clear() 133 | 134 | def replot_betsi(self): 135 | self.betsi_widget.run_calculation() 136 | 137 | def trigger_betsimagic(self, state): 138 | if state: 139 | self.betsi_widget.set_defaults() 140 | self.betsi_widget.set_editable(not state) 141 | 142 | def closeEvent(self, evt): 143 | print('Closing') 144 | try: 145 | plt.close(self.bet_widget.current_fig) 146 | plt.close(self.bet_widget.current_fig_2) 147 | except: 148 | pass 149 | 150 | 151 | class BETSI_widget(QWidget): 152 | """Widget containing all the options for running the BETSI analysis and display of the data. 153 | 154 | Args: 155 | parent: QMainWindow the widget belongs to 156 | 157 | """ 158 | 159 | def __init__(self, parent=None): 160 | super().__init__(parent) 161 | 162 | # basic properties 163 | self.output_dir = os.getcwd() 164 | self.current_fig = None 165 | self.current_fig_2 = None 166 | 167 | self.target_filepath = None 168 | self.bet_object = None 169 | self.bet_filter_result = None 170 | 171 | # Create label boxes to display info 172 | self.current_output_label = QLabel() 173 | self.current_targetpath_label = QLabel() 174 | 175 | self.current_output_label.setText( 176 | f"Output Directory: {self.output_dir}") 177 | self.current_targetpath_label.setText( 178 | f"Loaded File: {self.target_filepath}") 179 | self.current_output_label.setFont(QtGui.QFont("Times", 10)) 180 | self.current_targetpath_label.setFont(QtGui.QFont("Times", 10)) 181 | 182 | self.current_output_label.setWordWrap(True) 183 | self.current_output_label.setAlignment(QtCore.Qt.AlignLeft) 184 | #self.current_output_label.setStyleSheet("background-color: lightgreen") 185 | self.current_targetpath_label.setWordWrap(True) 186 | self.current_targetpath_label.setAlignment(QtCore.Qt.AlignLeft) 187 | #self.current_targetpath_label.setStyleSheet("background-color: lightblue") 188 | 189 | # add a group box containing controls 190 | self.criteria_box = QGroupBox("BET area selection criteria") 191 | self.criteria_box.setMaximumWidth(700) 192 | self.criteria_box.setMaximumHeight(1000) 193 | self.criteria_box.setMinimumWidth(500) 194 | #self.criteria_box.setMinimumHeight(650) 195 | self.min_points_label = QLabel(self.criteria_box) 196 | self.min_points_label.setText('Minimum number of points in the linear region: [3,10]') 197 | self.min_points_edit = QLineEdit() 198 | self.min_points_edit.setMaximumWidth(75) 199 | self.min_points_slider = QSlider(QtCore.Qt.Horizontal) 200 | self.minr2_label = QLabel('Minimum R2: [0.8,0.999]') 201 | self.minr2_edit = QLineEdit() 202 | self.minr2_edit.setMaximumWidth(75) 203 | self.minr2_slider = QSlider(QtCore.Qt.Horizontal) 204 | self.rouq1_tick = QCheckBox("Rouquerol criterion 1: Monotonic") 205 | self.rouq2_tick = QCheckBox("Rouquerol criterion 2: Positive C") 206 | self.rouq3_tick = QCheckBox("Rouquerol criterion 3: Pressure in linear range") 207 | self.rouq4_tick = QCheckBox("Rouquerol criterion 4: Error in %, [5,75]") 208 | self.rouq5_tick = QCheckBox("BETSI criterion: End at the knee") 209 | self.rouq4_edit = QLineEdit() 210 | self.rouq4_edit.setMaximumWidth(75) 211 | self.rouq4_slider = QSlider(QtCore.Qt.Horizontal) 212 | 213 | self.adsorbate_label = QLabel('Adsorbate:') 214 | self.adsorbate_combo_box = QComboBox() 215 | self.adsorbate_combo_box.addItems(["N2", "Ar", "Kr", "Xe", "Custom"]) 216 | self.adsorbate_cross_section_label = QLabel('Cross sectional area (nm2):') 217 | self.adsorbate_cross_section_edit = QLineEdit() 218 | self.adsorbate_cross_section_edit.setMaximumWidth(75) 219 | self.adsorbate_molar_volume_label = QLabel('Molar volume (mol/m3):') 220 | self.adsorbate_molar_volume_edit = QLineEdit() 221 | self.adsorbate_molar_volume_edit.setMaximumWidth(75) 222 | self.note_label = QLabel( 223 | """Hints: 224 | - For convenience, it is best to first set your desired criteria before importing the input file. 225 | - Drag and drop the input file into the BETSI window. 226 | - Valid input file formats: 227 | # Adsorption Information File: *.aif 228 | # Two-column data files: *.csv, *.txt 229 | # Micromeritics: *.XLS 230 | - Valid value range for parameters are given in brackets "[ ]" 231 | - Make sure to read the warnings that may pop up after BET calculation. 232 | - After the first run, by modifiying any of the parameters above, the calculations will rerun automatically. 233 | - Regarding the minimum number of points, Rouquerol suggested 10 points, but you can lower the number if the data has insufficient number of points. 234 | - Units: "Relative pressure" is dimensionless and "Quantity adsorbed" is in (cm\u00B3 STP/g). 235 | - When the calculation is done, by clicking on any points on the "Filtered BET Areas" plot, all the plots will change to the corresponding selected points range. 236 | - If the calculation takes longer than expected, bear with it. In case you see "Not responding", it means it is still running, otherwise the software would crash. 237 | """) 238 | self.note_label.setStyleSheet("background-color: lightblue") 239 | self.note_label.setMargin(10) 240 | #self.note_label.setIndent(10) 241 | self.note_label.setWordWrap(True) 242 | self.note_label.setAlignment(QtCore.Qt.AlignLeft) 243 | 244 | 245 | self.export_button = QPushButton('Export Results') 246 | self.export_button.pressed.connect(self.export) 247 | 248 | # any change in states updates the display. 249 | self.rouq1_tick.stateChanged.connect(self.maybe_run_calculation) 250 | self.rouq2_tick.stateChanged.connect(self.maybe_run_calculation) 251 | self.rouq3_tick.stateChanged.connect(self.maybe_run_calculation) 252 | self.rouq4_tick.stateChanged.connect(self.maybe_run_calculation) 253 | self.rouq5_tick.stateChanged.connect(self.maybe_run_calculation) 254 | 255 | # define widget parameters 256 | self.rouq4_slider.setRange(5, 75) 257 | self.minr2_slider.setRange(800, 999) 258 | self.min_points_slider.setRange(3, 10) 259 | 260 | # set the defaults 261 | self.set_defaults() 262 | 263 | # connect the actions 264 | self.minr2_edit.editingFinished.connect(self.minr2_edit_changed) 265 | ##self.minr2_edit.returnPressed.connect(self.minr2_edit_changed) 266 | self.rouq4_edit.editingFinished.connect(self.rouq4_edit_changed) 267 | ##self.rouq4_edit.returnPressed.connect(self.rouq4_edit_changed) 268 | self.min_points_edit.editingFinished.connect( 269 | self.min_points_edit_changed) 270 | ##self.min_points_edit.returnPressed.connect( 271 | ## self.min_points_edit_changed) 272 | ##self.rouq4_slider.valueChanged.connect(self.rouq4_slider_changed) 273 | ##self.minr2_slider.valueChanged.connect(self.minr2_slider_changed) 274 | ##self.min_points_slider.valueChanged.connect( 275 | ## self.min_points_slider_changed) 276 | 277 | 278 | self.adsorbate_combo_box.activated.connect(self.adsorbate_combo_box_changed) 279 | ##self.adsorbate_cross_section_edit.returnPressed.connect(self.adsorbate_related_edit_changed) 280 | self.adsorbate_cross_section_edit.editingFinished.connect(self.adsorbate_related_edit_changed) 281 | 282 | self.adsorbate_molar_volume_edit.editingFinished.connect(self.adsorbate_related_edit_changed) 283 | ##self.adsorbate_molar_volume_edit.returnPressed.connect(self.adsorbate_related_edit_changed) 284 | 285 | 286 | ##self.minr2_edit.editingFinished.connect(self.line_edit_changed) 287 | ##self.rouq4_edit.editingFinished.connect(self.line_edit_changed) 288 | ##self.min_points_edit.editingFinished.connect( 289 | ## self.line_edit_changed) 290 | ##self.adsorbate_cross_section_edit.editingFinished.connect(self.line_edit_changed) 291 | ##self.adsorbate_molar_volume_edit.editingFinished.connect(self.line_edit_changed) 292 | 293 | 294 | # add the table for results 295 | self.results_table = QTableWidget(self) 296 | self.results_table.setFixedWidth(520) 297 | self.clean_table() 298 | 299 | # create layout 300 | criteria_layout = QGridLayout() 301 | criteria_layout.addWidget( 302 | self.min_points_label, criteria_layout.rowCount(), 1, 1, 1) 303 | criteria_layout.addWidget( 304 | self.min_points_edit, criteria_layout.rowCount() - 1, 2) 305 | ##criteria_layout.addWidget( 306 | ## self.min_points_slider, criteria_layout.rowCount(), 1, 1, 2) 307 | criteria_layout.addWidget( 308 | self.minr2_label, criteria_layout.rowCount(), 1) 309 | criteria_layout.addWidget( 310 | self.minr2_edit, criteria_layout.rowCount() - 1, 2) 311 | ##criteria_layout.addWidget( 312 | ## self.minr2_slider, criteria_layout.rowCount(), 1, 1, 2) 313 | criteria_layout.addWidget( 314 | self.rouq1_tick, criteria_layout.rowCount(), 1) 315 | criteria_layout.addWidget( 316 | self.rouq2_tick, criteria_layout.rowCount(), 1) 317 | criteria_layout.addWidget( 318 | self.rouq3_tick, criteria_layout.rowCount(), 1) 319 | criteria_layout.addWidget( 320 | self.rouq4_tick, criteria_layout.rowCount(), 1) 321 | criteria_layout.addWidget( 322 | self.rouq4_edit, criteria_layout.rowCount() - 1, 2) 323 | ##criteria_layout.addWidget( 324 | ## self.rouq4_slider, criteria_layout.rowCount(), 1, 1, 2) 325 | criteria_layout.addWidget( 326 | self.rouq5_tick, criteria_layout.rowCount(), 1) 327 | 328 | criteria_layout.addWidget( 329 | self.adsorbate_label, criteria_layout.rowCount(), 1) 330 | criteria_layout.addWidget( 331 | self.adsorbate_combo_box, criteria_layout.rowCount() - 1, 2) 332 | criteria_layout.addWidget( 333 | self.adsorbate_cross_section_label, criteria_layout.rowCount(), 1) 334 | criteria_layout.addWidget( 335 | self.adsorbate_cross_section_edit, criteria_layout.rowCount() - 1, 2) 336 | criteria_layout.addWidget( 337 | self.adsorbate_molar_volume_label, criteria_layout.rowCount(), 1) 338 | criteria_layout.addWidget( 339 | self.adsorbate_molar_volume_edit, criteria_layout.rowCount() - 1, 2) 340 | 341 | criteria_layout.addWidget( 342 | self.note_label, criteria_layout.rowCount(), 1, 1, 2) 343 | 344 | criteria_layout.addWidget( 345 | self.export_button, criteria_layout.rowCount(), 1, 1, 2) 346 | 347 | # criteria_layout.addWidget( 348 | # self.current_output_label, criteria_layout.rowCount(), 1, 1, 1) 349 | # criteria_layout.addWidget( 350 | # self.current_targetpath_label, criteria_layout.rowCount(), 1, 1, 1) 351 | criteria_layout.addWidget( 352 | self.current_output_label, criteria_layout.rowCount(), 1, 1, 2) 353 | criteria_layout.addWidget( 354 | self.current_targetpath_label, criteria_layout.rowCount(), 1, 1, 2) 355 | 356 | 357 | 358 | criteria_layout.addItem(QSpacerItem( 359 | 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding), criteria_layout.rowCount() + 1, 1) 360 | self.criteria_box.setLayout(criteria_layout) 361 | 362 | main_layout = QGridLayout() 363 | main_layout.addWidget(self.criteria_box, 1, 1) 364 | main_layout.addWidget(self.results_table, 1, 2) 365 | 366 | self.setLayout(main_layout) 367 | 368 | def set_output_dir(self): 369 | """Defines the output directory of the BETSI analysis""" 370 | dir_path = QFileDialog.getExistingDirectory( 371 | self, 'Select Output Directory', os.getcwd()) 372 | print(f"Output directory set to {dir_path}") 373 | self.output_dir = dir_path 374 | self.current_output_label.setText( 375 | f"Output Directory: {self.output_dir}") 376 | 377 | def maybe_run_calculation(self): 378 | self.check_rouq_compatibility() 379 | 380 | self.check_adsorbate_compatibility() 381 | ##if self.target_filepath is not None: 382 | if self.target_filepath is not None: 383 | if self.adsorbate_combo_box.currentText() != "Custom": 384 | self.run_calculation() 385 | elif float(self.adsorbate_cross_section_edit.text()) > 0 and float(self.adsorbate_molar_volume_edit.text()) > 0: 386 | self.run_calculation() 387 | 388 | def plot_bet(self): 389 | # if plt.get_fignums() != [1, 2]: 390 | # plt.close('all') 391 | plt.close('all') 392 | if self.current_fig is not None: 393 | self.current_fig.clear() 394 | self.current_fig_2.clear() 395 | try: 396 | # check if the figure has been closed, if it doesn't reset it to none and replot 397 | if self.current_fig is not None and not plt.fignum_exists(self.current_fig.number): 398 | self.current_fig = None 399 | self.current_fig_2 = None 400 | fig = create_matrix_plot(self.bet_filter_result, self.rouq3_tick.isChecked(), self.rouq4_tick.isChecked(), name=Path( 401 | self.target_filepath).stem, fig=self.current_fig) 402 | fig_2 = regression_diagnostics_plots(self.bet_filter_result, name=Path( 403 | self.target_filepath).stem, fig_2=self.current_fig_2) 404 | # connect the picker event to the figure 405 | if self.current_fig is None: 406 | fig.canvas.mpl_connect('pick_event', self.point_picker) 407 | self.current_fig = fig 408 | self.current_fig.tight_layout(pad=0.3, rect=[0, 0, 1, 0.95]) 409 | self.current_fig_2 = fig_2 410 | plt.figure(num=1) 411 | plt.draw() 412 | plt.figure(num=2).canvas.manager.window.move(500,0) 413 | plt.draw() 414 | except TypeError: 415 | pass 416 | 417 | def run_calculation(self): 418 | """ Applies the currently specified filters to the currently specified target csv file. """ 419 | 420 | ## assert self.target_filepath, "You must provide a csv file before calling run." 421 | if not self.target_filepath: 422 | warnings = "You must provide an input file (e.g. *.csv, *.txt, *.aif, *.XLS) before calling run. Press \"Clear\" and try again. Please refer to the \"Hints\" box for a quick guide." 423 | information = "" 424 | self.show_dialog(warnings, information) 425 | return 426 | 427 | use_rouq1 = self.rouq1_tick.isChecked() 428 | use_rouq2 = self.rouq2_tick.isChecked() 429 | use_rouq3 = self.rouq3_tick.isChecked() 430 | use_rouq4 = self.rouq4_tick.isChecked() 431 | use_rouq5 = self.rouq5_tick.isChecked() 432 | min_num_pts = int(self.min_points_edit.text()) 433 | min_r2 = float(self.minr2_edit.text()) 434 | max_perc_error = float(self.rouq4_edit.text()) 435 | adsorbate = self.adsorbate_combo_box.currentText() 436 | cross_sectional_area = float(self.adsorbate_cross_section_edit.text()) 437 | molar_volume = float(self.adsorbate_molar_volume_edit.text()) 438 | 439 | 440 | # Retrieve the BETSI results object if non-existent 441 | if self.bet_object is None: 442 | pressure, q_adsorbed, comments_to_data = get_data(input_file=self.target_filepath) 443 | if len(pressure) == 0 or len(q_adsorbed) == 0: 444 | warnings = "You must provide a valid input file. BETSI cannot read it. Press \"Clear\" and try again with a valid one." 445 | information = "" 446 | self.show_dialog(warnings, information) 447 | return 448 | self.bet_object = BETResult(pressure, q_adsorbed) 449 | self.bet_object.comments_to_data = comments_to_data 450 | self.bet_object.original_pressure_data = pressure 451 | self.bet_object.original_q_adsorbed_data = q_adsorbed 452 | 453 | 454 | # Apply the currently selected filters. 455 | self.bet_filter_result = BETFilterAppliedResults(self.bet_object, 456 | min_num_pts=min_num_pts, 457 | min_r2=min_r2, 458 | use_rouq1=use_rouq1, 459 | use_rouq2=use_rouq2, 460 | use_rouq3=use_rouq3, 461 | use_rouq4=use_rouq4, 462 | use_rouq5=use_rouq5, 463 | max_perc_error=max_perc_error, 464 | adsorbate=adsorbate, 465 | cross_sectional_area=cross_sectional_area, 466 | molar_volume=molar_volume) 467 | 468 | ## Adds interpolated points to adsorption data if no valid area was found by the original data 469 | if not self.bet_filter_result.has_valid_areas: 470 | iter_num = 0 471 | self.bet_object.comments_to_data['interpolated_points_added'] = True 472 | while (not self.bet_filter_result.has_valid_areas) and (iter_num < 20): 473 | print('Adding extra interpolated points to the data') 474 | 475 | ##pressure = self.bet_object.pressure 476 | ##q_adsorbed = self.bet_object.q_adsorbed 477 | comments_to_data = self.bet_object.comments_to_data 478 | interpolated_points_added = self.bet_object.comments_to_data['interpolated_points_added'] 479 | original_pressure_data = self.bet_object.original_pressure_data 480 | original_q_adsorbed_data = self.bet_object.original_q_adsorbed_data 481 | 482 | self.bet_object = None 483 | self.bet_filter_result = None 484 | pressure_added_points, q_adsorbed_added_points = isotherm_pchip_reconstruction(original_pressure_data, original_q_adsorbed_data, (iter_num+1)*round(len(original_pressure_data)/1.5)) 485 | self.bet_object = BETResult(pressure_added_points, q_adsorbed_added_points) 486 | self.bet_object.comments_to_data = comments_to_data 487 | self.bet_object.comments_to_data['interpolated_points_added'] = interpolated_points_added 488 | self.bet_object.original_pressure_data = original_pressure_data 489 | self.bet_object.original_q_adsorbed_data = original_q_adsorbed_data 490 | 491 | # Apply the currently selected filters. 492 | self.bet_filter_result = BETFilterAppliedResults(self.bet_object, 493 | min_num_pts=min_num_pts, 494 | min_r2=min_r2, 495 | use_rouq1=use_rouq1, 496 | use_rouq2=use_rouq2, 497 | use_rouq3=use_rouq3, 498 | use_rouq4=use_rouq4, 499 | use_rouq5=use_rouq5, 500 | max_perc_error=max_perc_error, 501 | adsorbate=adsorbate, 502 | cross_sectional_area=cross_sectional_area, 503 | molar_volume=molar_volume) 504 | iter_num += 1 505 | 506 | ## Warnings for pop-up message box 507 | warnings = "" 508 | information = "" 509 | if self.bet_object.comments_to_data['has_negative_pressure_points']: 510 | warnings += "- Imported adsorption data has negative pressure point(s)!\n" 511 | information += "- Negative pressure point(s) have been removed from the data.\n" 512 | if not self.bet_object.comments_to_data['monotonically_increasing_pressure']: 513 | warnings += "- The pressure points are not monotonically increasing!\n" 514 | information += "- Non-monotonically increasing pressure point(s) have been removed from the data.\n" 515 | if not self.bet_object.comments_to_data['rel_pressure_between_0_and_1']: 516 | warnings += "- The relative pressure values must lie between 0 and 1!\n" 517 | information += "- Relative pressure point(s) above 1.0 have been removed from the data.\n" 518 | if self.bet_object.comments_to_data['interpolated_points_added']: 519 | warnings += "- No valid areas found with the chosen minimum number of points! So, interpolated points are added to the data!\n" 520 | 521 | if self.bet_filter_result.has_valid_areas: 522 | self.plot_bet() 523 | if warnings != "": 524 | warnings = "Consider the following warning(s):\n" + warnings 525 | if information != "": 526 | information = "Note(s):\n" + information 527 | self.show_dialog(warnings, information) 528 | else: 529 | if warnings == "": 530 | warnings = "No valid areas found! Try again with a new set of data and/or change your criteria" 531 | self.show_dialog(warnings, information) 532 | else: 533 | warnings_1 = "No valid areas found! Try again with a new set of data and/or change your criteria.\n" 534 | warnings_2 = "Consider the following warning(s):\n" 535 | warnings = warnings_1 + warnings_2 + warnings 536 | information = "Note(s):\n" + information 537 | self.show_dialog(warnings, information) 538 | 539 | 540 | def show_dialog(self, warnings, information): 541 | dialog = QMessageBox() 542 | dialog.setText(warnings) 543 | dialog.setWindowTitle('Warnings') 544 | if warnings.find("No valid areas found!") != -1: 545 | dialog.setIcon(QMessageBox().Critical) 546 | dialog.setStandardButtons(QMessageBox.Ok) 547 | dialog.addButton("Clear", QMessageBox.AcceptRole) 548 | if information != "": 549 | information += "\n\nPress \"Clear\" if you want to clear input data, reset to default values and start over with a new set of data.\n" 550 | else: 551 | information = "Press \"Clear\" if you want to clear input data, reset to default values and start over with a new set of data.\n" 552 | elif warnings.find("You must provide a") != -1: 553 | dialog.setIcon(QMessageBox().Critical) 554 | dialog.addButton("Clear", QMessageBox.AcceptRole) 555 | else: 556 | dialog.setIcon(QMessageBox().Warning) 557 | dialog.setInformativeText(information) 558 | 559 | dialog.buttonClicked.connect(self.dialog_clicked) 560 | dialog.exec_() 561 | 562 | def dialog_clicked(self, dialog_button): 563 | if dialog_button.text() == "Clear": 564 | self.clear() 565 | 566 | def point_picker(self, event): 567 | line = event.artist 568 | picked_coords = line.get_offsets()[event.ind][0] 569 | # redefine min_i and min_j 570 | self.bet_filter_result.find_nearest_idx(picked_coords) 571 | # replot based on the new min_i and min_j 572 | self.plot_bet() 573 | 574 | def export(self): 575 | """ Print out the plot, filter config and results to the output directory. """ 576 | if self.bet_filter_result is not None: 577 | 578 | # Create a local sub-directory for export. 579 | target_path = Path(self.target_filepath) 580 | output_subdir = Path(self.output_dir) / target_path.name 581 | output_subdir.mkdir(exist_ok=True) 582 | 583 | self.bet_filter_result.export(output_subdir) 584 | 585 | self.current_fig.savefig(str(output_subdir / f'{target_path.stem}_plot.pdf'), bbox_inches='tight') 586 | plt.show() 587 | self.current_fig_2.savefig(str(output_subdir / f'{target_path.stem}_RD_plots.pdf')) 588 | plt.show() 589 | 590 | def analyse_directory(self, dir_path): 591 | """ Run BETSI on all csv files within dir_path. Use current filter config.""" 592 | use_rouq1 = self.rouq1_tick.isChecked() 593 | use_rouq2 = self.rouq2_tick.isChecked() 594 | use_rouq3 = self.rouq3_tick.isChecked() 595 | use_rouq4 = self.rouq4_tick.isChecked() 596 | use_rouq5 = self.rouq5_tick.isChecked() 597 | min_num_points = int(self.min_points_edit.text()) 598 | min_r2 = float(self.minr2_edit.text()) 599 | max_perc_error = float(self.rouq4_edit.text()) 600 | 601 | adsorbate = self.adsorbate_combo_box.currentText() 602 | cross_sectional_area = float(self.adsorbate_cross_section_edit.text()) 603 | molar_volume = float(self.adsorbate_molar_volume_edit.text()) 604 | 605 | 606 | ##csv_paths = Path(dir_path).glob('*.csv') 607 | csv_paths = Path(dir_path).glob('*.csv') 608 | aif_paths = Path(dir_path).glob('*.aif') 609 | txt_paths = Path(dir_path).glob('*.txt') 610 | XLS_paths = Path(dir_path).glob('*.XLS') 611 | input_file_paths = (*csv_paths, *aif_paths, *txt_paths, *XLS_paths) 612 | 613 | ##for file_path in csv_paths: 614 | for file_path in input_file_paths: 615 | # Update the table with current file 616 | self.populate_table(csv_path=str(file_path)) 617 | 618 | # Run the analysis 619 | analyse_file(input_file=str(file_path), 620 | output_dir=self.output_dir, 621 | min_num_pts=min_num_points, 622 | min_r2=min_r2, 623 | use_rouq1=use_rouq1, 624 | use_rouq2=use_rouq2, 625 | use_rouq3=use_rouq3, 626 | use_rouq4=use_rouq4, 627 | use_rouq5=use_rouq5, 628 | max_perc_error=max_perc_error, 629 | adsorbate=adsorbate, 630 | cross_sectional_area=cross_sectional_area, 631 | molar_volume=molar_volume) 632 | 633 | def set_defaults(self): 634 | """Sets the widget to default state 635 | """ 636 | # set default values for the tick marks 637 | self.rouq1_tick.setCheckState(True) 638 | self.rouq2_tick.setCheckState(True) 639 | self.rouq3_tick.setCheckState(True) 640 | self.rouq4_tick.setCheckState(True) 641 | self.rouq5_tick.setCheckState(True) 642 | 643 | # the ticks can only be on or off - Not sure why I need to do this every time, but doesn't matter 644 | self.rouq1_tick.setTristate(False) 645 | self.rouq2_tick.setTristate(False) 646 | self.rouq3_tick.setTristate(False) 647 | self.rouq4_tick.setTristate(False) 648 | self.rouq5_tick.setTristate(False) 649 | # set defaults for text fields 650 | self.minr2_edit.setText('0.995') 651 | self.rouq4_edit.setText('20') 652 | self.min_points_edit.setText('10') 653 | 654 | 655 | ## set defaults for adsorbate related parameters 656 | self.adsorbate_combo_box.setCurrentIndex(0) 657 | #self.adsorbate_cross_section_edit.setText('0.0') 658 | #self.adsorbate_molar_volume_edit.setText('0.0') 659 | self.adsorbate_cross_section_edit.setText(str(cross_sectional_area[self.adsorbate_combo_box.currentText()] * 1.0E18)) 660 | self.adsorbate_molar_volume_edit.setText(str(mol_vol[self.adsorbate_combo_box.currentText()])) 661 | 662 | 663 | self.line_edit_values_before = {"minr2_edit": self.minr2_edit.text(), \ 664 | "rouq4_edit": self.rouq4_edit.text(), \ 665 | "min_points_edit": self.min_points_edit.text(), \ 666 | "adsorbate_cross_section_edit": self.adsorbate_cross_section_edit.text(), \ 667 | "adsorbate_molar_volume_edit": self.adsorbate_molar_volume_edit.text()} 668 | 669 | 670 | # trigger the corresponding sliders 671 | self.rouq4_edit_changed() 672 | self.minr2_edit_changed() 673 | self.min_points_edit_changed() 674 | # check the compatibility 675 | self.check_rouq_compatibility() 676 | 677 | ## check the compatibility for adsorbate 678 | self.check_adsorbate_compatibility() 679 | 680 | # if there is a figure, replot 681 | self.plot_bet() 682 | 683 | def clear(self): 684 | """Closes all plots and removes all data from memory""" 685 | # remove the bet filter result from memory 686 | self.bet_filter_result = None 687 | self.bet_object = None 688 | # clear the table 689 | self.clean_table() 690 | # close the plot 691 | if self.current_fig is not None: 692 | plt.close(fig=self.current_fig) 693 | plt.close(fig=self.current_fig_2) 694 | 695 | 696 | ##reset the parameters to defaults 697 | self.target_filepath = None 698 | self.current_targetpath_label.setText( 699 | f"Loaded File: {self.target_filepath}") 700 | self.set_defaults() 701 | 702 | def set_editable(self, state): 703 | if state: 704 | self.rouq1_tick.setEnabled(True) 705 | self.rouq2_tick.setEnabled(True) 706 | self.rouq3_tick.setEnabled(True) 707 | self.rouq4_tick.setEnabled(True) 708 | self.rouq5_tick.setEnabled(True) 709 | self.minr2_edit.setEnabled(True) 710 | self.rouq4_edit.setEnabled(True) 711 | self.min_points_edit.setEnabled(True) 712 | self.rouq4_slider.setEnabled(True) 713 | self.minr2_slider.setEnabled(True) 714 | self.min_points_slider.setEnabled(True) 715 | self.adsorbate_combo_box.setEnabled(True) 716 | self.adsorbate_cross_section_edit.setEnabled(True) 717 | self.adsorbate_molar_volume_edit.setEnabled(True) 718 | else: 719 | self.rouq1_tick.setEnabled(False) 720 | self.rouq2_tick.setEnabled(False) 721 | self.rouq3_tick.setEnabled(False) 722 | self.rouq4_tick.setEnabled(False) 723 | self.rouq5_tick.setEnabled(False) 724 | self.minr2_edit.setEnabled(False) 725 | self.rouq4_edit.setEnabled(False) 726 | self.min_points_edit.setEnabled(False) 727 | self.rouq4_slider.setEnabled(False) 728 | self.minr2_slider.setEnabled(False) 729 | self.min_points_slider.setEnabled(False) 730 | self.adsorbate_combo_box.setEnabled(False) 731 | self.adsorbate_cross_section_edit.setEnabled(False) 732 | self.adsorbate_molar_volume_edit.setEnabled(False) 733 | 734 | def check_rouq_compatibility(self): 735 | use_rouq1 = self.rouq1_tick.isChecked() 736 | use_rouq2 = self.rouq2_tick.isChecked() 737 | use_rouq3 = self.rouq3_tick.isChecked() 738 | use_rouq4 = self.rouq4_tick.isChecked() 739 | use_rouq5 = self.rouq5_tick.isChecked() 740 | if not (use_rouq3 or use_rouq4): 741 | self.rouq2_tick.setEnabled(True) 742 | else: 743 | self.rouq2_tick.setChecked(True) 744 | self.rouq2_tick.setEnabled(False) 745 | 746 | def check_adsorbate_compatibility(self): 747 | if self.adsorbate_combo_box.currentText() != "Custom": 748 | self.adsorbate_cross_section_edit.setEnabled(False) 749 | self.adsorbate_molar_volume_edit.setEnabled(False) 750 | self.adsorbate_cross_section_edit.setText("{0:0.3f}".format(cross_sectional_area[self.adsorbate_combo_box.currentText()] * 1.0E18)) 751 | self.adsorbate_molar_volume_edit.setText("{0:0.3f}".format(mol_vol[self.adsorbate_combo_box.currentText()])) 752 | #self.adsorbate_cross_section_edit.setText("0.0") 753 | #self.adsorbate_molar_volume_edit.setText("0.0") 754 | else: 755 | self.adsorbate_cross_section_edit.setEnabled(True) 756 | self.adsorbate_molar_volume_edit.setEnabled(True) 757 | 758 | def populate_table(self, csv_path): 759 | self.results_table.setColumnCount(2) 760 | self.results_table.setRowCount(1) 761 | self.results_table.setHorizontalHeaderLabels( 762 | ['Relative pressure (p/p\u2080)', 'Quantity adsorbed (cm\u00B3/g)']) 763 | self.results_table.setColumnWidth(0, 250) 764 | self.results_table.setColumnWidth(1, 250) 765 | 766 | # Change the box title 767 | self.current_targetpath_label.setText( 768 | f"Loaded File: {self.target_filepath}") 769 | 770 | if csv_path is not None and Path(csv_path).exists(): 771 | pressure, q_adsorbed, comments_to_data = get_data(input_file=csv_path) 772 | self.results_table.setRowCount(len(pressure)) 773 | for i in range(len(pressure)): 774 | self.results_table.setItem( 775 | i, 0, QTableWidgetItem(str(pressure[i]))) 776 | self.results_table.setItem( 777 | i, 1, QTableWidgetItem(str(q_adsorbed[i]))) 778 | 779 | def clean_table(self): 780 | """Cleans the table""" 781 | self.results_table.setColumnCount(2) 782 | self.results_table.setRowCount(0) 783 | self.results_table.setHorizontalHeaderLabels( 784 | ['Relative pressure (p/p\u2080)', 'Quantity adsorbed (cm\u00B3 STP/g)']) 785 | self.results_table.setColumnWidth(0, 250) 786 | self.results_table.setColumnWidth(1, 250) 787 | 788 | 789 | def minr2_edit_changed(self): 790 | value = self.minr2_edit.text() 791 | mn = self.minr2_slider.minimum() 792 | mx = self.minr2_slider.maximum() 793 | try: 794 | value = int(round(float(value) * 1000)) 795 | if value < mn: 796 | value = mn 797 | self.minr2_edit.setText(str(value / 1000)) 798 | elif value > mx: 799 | value = mx 800 | self.minr2_edit.setText(str(value / 1000)) 801 | self.minr2_slider.setValue(value) 802 | except (ValueError, TypeError): 803 | self.minr2_edit.setText('0.995') 804 | if self.line_edit_values_before["minr2_edit"] != self.minr2_edit.text(): 805 | self.line_edit_values_before["minr2_edit"] = self.minr2_edit.text() 806 | self.maybe_run_calculation() 807 | 808 | def rouq4_edit_changed(self): 809 | value = self.rouq4_edit.text() 810 | mn = self.rouq4_slider.minimum() 811 | mx = self.rouq4_slider.maximum() 812 | try: 813 | value = int(float(value)) 814 | if value < mn: 815 | value = mn 816 | self.rouq4_edit.setText(str(value)) 817 | if value > mx: 818 | value = mx 819 | self.rouq4_edit.setText(str(value)) 820 | self.rouq4_slider.setValue(value) 821 | except (ValueError, TypeError): 822 | self.rouq4_edit.setText('20') 823 | if self.line_edit_values_before["rouq4_edit"] != self.rouq4_edit.text(): 824 | self.line_edit_values_before["rouq4_edit"] = self.rouq4_edit.text() 825 | self.maybe_run_calculation() 826 | 827 | def min_points_edit_changed(self): 828 | value = self.min_points_edit.text() 829 | mn = self.min_points_slider.minimum() 830 | mx = self.min_points_slider.maximum() 831 | try: 832 | value = int(round(float(value))) 833 | if value < mn: 834 | value = mn 835 | self.min_points_edit.setText(str(value)) 836 | if value > mx: 837 | value = mx 838 | self.min_points_edit.setText(str(value)) 839 | self.min_points_slider.setValue(value) 840 | except (ValueError, TypeError): 841 | self.min_points_edit.setText('10') 842 | if self.line_edit_values_before["min_points_edit"] != self.min_points_edit.text(): 843 | self.line_edit_values_before["min_points_edit"] = self.min_points_edit.text() 844 | self.maybe_run_calculation() 845 | 846 | def rouq4_slider_changed(self): 847 | value = self.rouq4_slider.value() 848 | self.rouq4_edit.setText(str(value)) 849 | self.maybe_run_calculation() 850 | 851 | def minr2_slider_changed(self): 852 | value = self.minr2_slider.value() 853 | self.minr2_edit.setText(str(value / 1000)) 854 | self.maybe_run_calculation() 855 | 856 | def min_points_slider_changed(self): 857 | value = self.min_points_slider.value() 858 | self.min_points_edit.setText(str(value)) 859 | self.maybe_run_calculation() 860 | 861 | def adsorbate_related_edit_changed(self): 862 | if not self.is_float(self.adsorbate_cross_section_edit.text()): 863 | self.adsorbate_cross_section_edit.setText("0.0") 864 | if not self.is_float(self.adsorbate_molar_volume_edit.text()): 865 | self.adsorbate_molar_volume_edit.setText("0.0") 866 | if self.line_edit_values_before["adsorbate_cross_section_edit"] != self.adsorbate_cross_section_edit.text() or \ 867 | self.line_edit_values_before["adsorbate_molar_volume_edit"] != self.adsorbate_molar_volume_edit.text(): 868 | self.line_edit_values_before["adsorbate_cross_section_edit"] = self.adsorbate_cross_section_edit.text() 869 | self.line_edit_values_before["adsorbate_molar_volume_edit"] = self.adsorbate_molar_volume_edit.text() 870 | self.maybe_run_calculation() 871 | 872 | def adsorbate_combo_box_changed(self): 873 | if self.adsorbate_combo_box.currentText() == "Custom": 874 | self.adsorbate_cross_section_edit.setText("0.0") 875 | self.adsorbate_molar_volume_edit.setText("0.0") 876 | self.maybe_run_calculation() 877 | 878 | def line_edit_changed(self): 879 | # modify_check = [] 880 | # modify_check.append(self.minr2_edit.isModified()) 881 | # modify_check.append(self.rouq4_edit.isModified()) 882 | # modify_check.append(self.min_points_edit.isModified()) 883 | # modify_check.append(self.adsorbate_cross_section_edit.isModified()) 884 | # modify_check.append(self.adsorbate_molar_volume_edit.isModified()) 885 | current_line_edit_values = self.get_line_edit_current_values() 886 | if self.line_edit_values_before != current_line_edit_values: 887 | self.maybe_run_calculation() 888 | self.line_edit_values_before = current_line_edit_values 889 | 890 | def get_line_edit_current_values(self): 891 | current_line_edit_values = [] 892 | current_line_edit_values.append(self.minr2_edit.text()) 893 | current_line_edit_values.append(self.rouq4_edit.text()) 894 | current_line_edit_values.append(self.min_points_edit.text()) 895 | current_line_edit_values.append(self.adsorbate_cross_section_edit.text()) 896 | current_line_edit_values.append(self.adsorbate_molar_volume_edit.text()) 897 | return current_line_edit_values 898 | 899 | def is_float(self, string): 900 | try: 901 | float(string) 902 | return True 903 | except ValueError: 904 | return False 905 | 906 | def __del__(self): 907 | try: 908 | plt.close(self.current_fig_2) 909 | plt.close(self.current_fig) 910 | except: 911 | pass 912 | 913 | class OutputCanvas(FigureCanvas): 914 | def __init__(self, parent, dpi=100): 915 | self.fig = Figure(dpi=dpi) 916 | self.fig.subplots_adjust(left=0.05, right=0.95) 917 | FigureCanvas.__init__(self, self.fig) 918 | self.setParent(parent) 919 | FigureCanvas.setSizePolicy(self, 920 | QSizePolicy.Expanding, 921 | QSizePolicy.Expanding) 922 | FigureCanvas.updateGeometry(self) 923 | 924 | class OutputCanvas_2(FigureCanvas): 925 | def __init__(self,parent,dpi=100): 926 | self.fig_2 = Figure(dpi=dpi) 927 | self.fig_2.subplots_adjust(left=.05, right=.95) 928 | FigureCanvas.__init__(self,self.fig_2) 929 | self.setParent(parent) 930 | FigureCanvas.setSizePolicy(self, 931 | QSizePolicy.Expanding, 932 | QSizePolicy.Expanding) 933 | FigureCanvas.updateGeometry(self) 934 | 935 | class OutLog(logging.Handler): 936 | def __init__(self, out_widget): 937 | """(edit, out=None, color=None) -> can write stdout, stderr to a 938 | QTextEdit. 939 | """ 940 | logging.Handler.__init__(self) 941 | self.out_widget = out_widget 942 | 943 | def emit(self, message): 944 | self.write(message.getMessage() + '\n') 945 | 946 | def write(self, m): 947 | self.out_widget.moveCursor(QtGui.QTextCursor.End) 948 | self.out_widget.insertPlainText(m) 949 | 950 | 951 | def runbetsi(): 952 | QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) 953 | app = QApplication(sys.argv) 954 | ex = BETSI_gui() 955 | ex.show() 956 | sys.exit(app.exec_()) 957 | 958 | runbetsi() --------------------------------------------------------------------------------