├── .gitattributes
├── .idea
├── .gitignore
├── betsi-gui.iml
├── betsi.iml
├── inspectionProfiles
│ └── profiles_settings.xml
├── markdown-navigator-enh.xml
├── markdown-navigator.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── .travis.yml
├── LICENSE.txt
├── MANIFEST.in
├── README.md
├── betsi
├── .idea
│ ├── .gitignore
│ ├── betsiv8.iml
│ ├── inspectionProfiles
│ │ └── profiles_settings.xml
│ ├── markdown-navigator-enh.xml
│ ├── markdown-navigator.xml
│ ├── misc.xml
│ ├── modules.xml
│ └── vcs.xml
├── __init__.py
├── __main__.py
├── __pycache__
│ ├── lib.cpython-37.pyc
│ ├── plotting.cpython-37.pyc
│ └── utils.cpython-37.pyc
├── data
│ ├── Al fumarate.csv
│ ├── DMOF-1.csv
│ ├── EXP_ZIF-8powder.csv
│ ├── HKUST-1.csv
│ ├── MCM-41.csv
│ ├── MIL-100.csv
│ ├── MIL-101.csv
│ ├── MOF-5.csv
│ ├── Mg-MOF-74.csv
│ ├── NU-1000.csv
│ ├── NU-1102.csv
│ ├── NU-1104.csv
│ ├── NU-1105.csv
│ ├── PCN-777.csv
│ ├── TPB-DMTP-COF.csv
│ ├── UiO-66-NH2.csv
│ ├── UiO-66.csv
│ ├── ZIF-8.csv
│ └── Zeolite-13X.csv
├── gui.py
├── lib.py
├── plotting.py
└── utils.py
├── cli.py
├── cli.spec
├── docs
└── images
│ ├── BETSI-logo.jpeg
│ ├── a2ml_logo.png
│ ├── betsi_logo.PNG
│ ├── executables-banner.PNG
│ ├── step-1.png
│ ├── step-10.png
│ ├── step-11.png
│ ├── step-12.png
│ ├── step-13.png
│ ├── step-14.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
├── executables
├── BETSI_v1.1.0_windows.exe
└── mac_executables.zip
├── requirements.txt
└── setup.py
/.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 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Default ignored files
3 | /workspace.xml
--------------------------------------------------------------------------------
/.idea/betsi-gui.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/betsi.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator.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 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | graft betsi
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
2 |
3 |
4 |
5 |
6 |
7 |
8 | [](https://travis-ci.com/nakulrampal/betsi-gui)
9 | [](https://aamplify.readthedocs.io/en/latest/?badge=latest)
10 | [](https://gitter.im/betsi-gui/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://github.com/nakulrampal/betsi-gui/pulse) [](https://github.com/nakulrampal/github-gui/LICENSE.txt)
11 |
12 |
13 |
14 |
15 |
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 | #
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 | #
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 | #
58 |
59 | This will prompt a command terminal in the new environment.
60 |
61 | #
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 |
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 | #
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 | #
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 | #
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 | #
97 |
98 | To output BETSI data, select an output directory and click ```Export Results``` in the GUI.
99 |
100 | #
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 | #
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 | #
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 |
--------------------------------------------------------------------------------
/betsi/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /workspace.xml
--------------------------------------------------------------------------------
/betsi/.idea/betsiv8.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/betsi/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/betsi/.idea/markdown-navigator.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 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/betsi/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/betsi/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/betsi/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/betsi/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/betsi/__init__.py
--------------------------------------------------------------------------------
/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()
--------------------------------------------------------------------------------
/betsi/__pycache__/lib.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/betsi/__pycache__/lib.cpython-37.pyc
--------------------------------------------------------------------------------
/betsi/__pycache__/plotting.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/betsi/__pycache__/plotting.cpython-37.pyc
--------------------------------------------------------------------------------
/betsi/__pycache__/utils.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/betsi/__pycache__/utils.cpython-37.pyc
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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/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/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/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
--------------------------------------------------------------------------------
/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/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/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/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
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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
--------------------------------------------------------------------------------
/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/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/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 | ## New lines added
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")[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 | ## New lines added
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'] 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 | ## new lines added
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']:
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 | ## New lines added
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(500)
192 | self.criteria_box.setMaximumHeight(800)
193 | self.criteria_box.setMinimumWidth(400)
194 | #self.criteria_box.setMaximumHeight(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("Rouquerol criterion 5: End at the knee")
209 | self.rouq4_edit = QLineEdit()
210 | self.rouq4_edit.setMaximumWidth(75)
211 | self.rouq4_slider = QSlider(QtCore.Qt.Horizontal)
212 | ## New lines added
213 | self.adsorbate_label = QLabel('Adsorbate:')
214 | self.adsorbate_combo_box = QComboBox()
215 | self.adsorbate_combo_box.addItems(["N2", "Ar", "Kr", "Xe", "CO2", "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: *.csv, *.txt, *.aif
227 | - Make sure to read the warnings that may pop up after BET calculation.
228 | - After the first run, by modifiying any of the parameters above, the calculations will rerun automatically.
229 | """)
230 | self.note_label.setStyleSheet("background-color: lightblue")
231 | self.note_label.setMargin(10)
232 | #self.note_label.setIndent(10)
233 | self.note_label.setWordWrap(True)
234 | self.note_label.setAlignment(QtCore.Qt.AlignLeft)
235 |
236 |
237 | self.export_button = QPushButton('Export Results')
238 | self.export_button.pressed.connect(self.export)
239 |
240 | # any change in states updates the display.
241 | self.rouq1_tick.stateChanged.connect(self.maybe_run_calculation)
242 | self.rouq2_tick.stateChanged.connect(self.maybe_run_calculation)
243 | self.rouq3_tick.stateChanged.connect(self.maybe_run_calculation)
244 | self.rouq4_tick.stateChanged.connect(self.maybe_run_calculation)
245 | self.rouq5_tick.stateChanged.connect(self.maybe_run_calculation)
246 |
247 | # define widget parameters
248 | self.rouq4_slider.setRange(5, 75)
249 | self.minr2_slider.setRange(800, 999)
250 | self.min_points_slider.setRange(3, 10)
251 |
252 | # set the defaults
253 | self.set_defaults()
254 |
255 | # connect the actions
256 | self.minr2_edit.editingFinished.connect(self.minr2_edit_changed)
257 | ##self.minr2_edit.returnPressed.connect(self.minr2_edit_changed)
258 | self.rouq4_edit.editingFinished.connect(self.rouq4_edit_changed)
259 | ##self.rouq4_edit.returnPressed.connect(self.rouq4_edit_changed)
260 | self.min_points_edit.editingFinished.connect(
261 | self.min_points_edit_changed)
262 | ##self.min_points_edit.returnPressed.connect(
263 | ## self.min_points_edit_changed)
264 | ##self.rouq4_slider.valueChanged.connect(self.rouq4_slider_changed)
265 | ##self.minr2_slider.valueChanged.connect(self.minr2_slider_changed)
266 | ##self.min_points_slider.valueChanged.connect(
267 | ## self.min_points_slider_changed)
268 |
269 | ## New lines added
270 | self.adsorbate_combo_box.activated.connect(self.adsorbate_combo_box_changed)
271 | ##self.adsorbate_cross_section_edit.returnPressed.connect(self.adsorbate_related_edit_changed)
272 | self.adsorbate_cross_section_edit.editingFinished.connect(self.adsorbate_related_edit_changed)
273 |
274 | self.adsorbate_molar_volume_edit.editingFinished.connect(self.adsorbate_related_edit_changed)
275 | ##self.adsorbate_molar_volume_edit.returnPressed.connect(self.adsorbate_related_edit_changed)
276 |
277 |
278 | ##self.minr2_edit.editingFinished.connect(self.line_edit_changed)
279 | ##self.rouq4_edit.editingFinished.connect(self.line_edit_changed)
280 | ##self.min_points_edit.editingFinished.connect(
281 | ## self.line_edit_changed)
282 | ##self.adsorbate_cross_section_edit.editingFinished.connect(self.line_edit_changed)
283 | ##self.adsorbate_molar_volume_edit.editingFinished.connect(self.line_edit_changed)
284 |
285 |
286 | # add the table for results
287 | self.results_table = QTableWidget(self)
288 | self.results_table.setFixedWidth(520)
289 | self.clean_table()
290 |
291 | # create layout
292 | criteria_layout = QGridLayout()
293 | criteria_layout.addWidget(
294 | self.min_points_label, criteria_layout.rowCount(), 1, 1, 1)
295 | criteria_layout.addWidget(
296 | self.min_points_edit, criteria_layout.rowCount() - 1, 2)
297 | ##criteria_layout.addWidget(
298 | ## self.min_points_slider, criteria_layout.rowCount(), 1, 1, 2)
299 | criteria_layout.addWidget(
300 | self.minr2_label, criteria_layout.rowCount(), 1)
301 | criteria_layout.addWidget(
302 | self.minr2_edit, criteria_layout.rowCount() - 1, 2)
303 | ##criteria_layout.addWidget(
304 | ## self.minr2_slider, criteria_layout.rowCount(), 1, 1, 2)
305 | criteria_layout.addWidget(
306 | self.rouq1_tick, criteria_layout.rowCount(), 1)
307 | criteria_layout.addWidget(
308 | self.rouq2_tick, criteria_layout.rowCount(), 1)
309 | criteria_layout.addWidget(
310 | self.rouq3_tick, criteria_layout.rowCount(), 1)
311 | criteria_layout.addWidget(
312 | self.rouq4_tick, criteria_layout.rowCount(), 1)
313 | criteria_layout.addWidget(
314 | self.rouq4_edit, criteria_layout.rowCount() - 1, 2)
315 | ##criteria_layout.addWidget(
316 | ## self.rouq4_slider, criteria_layout.rowCount(), 1, 1, 2)
317 | criteria_layout.addWidget(
318 | self.rouq5_tick, criteria_layout.rowCount(), 1)
319 | ## New lines added
320 | criteria_layout.addWidget(
321 | self.adsorbate_label, criteria_layout.rowCount(), 1)
322 | criteria_layout.addWidget(
323 | self.adsorbate_combo_box, criteria_layout.rowCount() - 1, 2)
324 | criteria_layout.addWidget(
325 | self.adsorbate_cross_section_label, criteria_layout.rowCount(), 1)
326 | criteria_layout.addWidget(
327 | self.adsorbate_cross_section_edit, criteria_layout.rowCount() - 1, 2)
328 | criteria_layout.addWidget(
329 | self.adsorbate_molar_volume_label, criteria_layout.rowCount(), 1)
330 | criteria_layout.addWidget(
331 | self.adsorbate_molar_volume_edit, criteria_layout.rowCount() - 1, 2)
332 |
333 | criteria_layout.addWidget(
334 | self.note_label, criteria_layout.rowCount(), 1, 1, 2)
335 |
336 | criteria_layout.addWidget(
337 | self.export_button, criteria_layout.rowCount(), 1, 1, 2)
338 |
339 | # criteria_layout.addWidget(
340 | # self.current_output_label, criteria_layout.rowCount(), 1, 1, 1)
341 | # criteria_layout.addWidget(
342 | # self.current_targetpath_label, criteria_layout.rowCount(), 1, 1, 1)
343 | criteria_layout.addWidget(
344 | self.current_output_label, criteria_layout.rowCount(), 1, 1, 2)
345 | criteria_layout.addWidget(
346 | self.current_targetpath_label, criteria_layout.rowCount(), 1, 1, 2)
347 |
348 |
349 |
350 | criteria_layout.addItem(QSpacerItem(
351 | 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding), criteria_layout.rowCount() + 1, 1)
352 | self.criteria_box.setLayout(criteria_layout)
353 |
354 | main_layout = QGridLayout()
355 | main_layout.addWidget(self.criteria_box, 1, 1)
356 | main_layout.addWidget(self.results_table, 1, 2)
357 |
358 | self.setLayout(main_layout)
359 |
360 | def set_output_dir(self):
361 | """Defines the output directory of the BETSI analysis"""
362 | dir_path = QFileDialog.getExistingDirectory(
363 | self, 'Select Output Directory', os.getcwd())
364 | print(f"Output directory set to {dir_path}")
365 | self.output_dir = dir_path
366 | self.current_output_label.setText(
367 | f"Output Directory: {self.output_dir}")
368 |
369 | def maybe_run_calculation(self):
370 | self.check_rouq_compatibility()
371 | ## New lines added
372 | self.check_adsorbate_compatibility()
373 | ##if self.target_filepath is not None:
374 | if self.target_filepath is not None:
375 | if self.adsorbate_combo_box.currentText() != "Custom":
376 | self.run_calculation()
377 | elif float(self.adsorbate_cross_section_edit.text()) > 0 and float(self.adsorbate_molar_volume_edit.text()) > 0:
378 | self.run_calculation()
379 |
380 | def plot_bet(self):
381 | # if plt.get_fignums() != [1, 2]:
382 | # plt.close('all')
383 | plt.close('all')
384 | if self.current_fig is not None:
385 | self.current_fig.clear()
386 | self.current_fig_2.clear()
387 | try:
388 | # check if the figure has been closed, if it doesn't reset it to none and replot
389 | if self.current_fig is not None and not plt.fignum_exists(self.current_fig.number):
390 | self.current_fig = None
391 | self.current_fig_2 = None
392 | fig = create_matrix_plot(self.bet_filter_result, self.rouq3_tick.isChecked(), self.rouq4_tick.isChecked(), name=Path(
393 | self.target_filepath).stem, fig=self.current_fig)
394 | fig_2 = regression_diagnostics_plots(self.bet_filter_result, name=Path(
395 | self.target_filepath).stem, fig_2=self.current_fig_2)
396 | # connect the picker event to the figure
397 | if self.current_fig is None:
398 | fig.canvas.mpl_connect('pick_event', self.point_picker)
399 | self.current_fig = fig
400 | self.current_fig.tight_layout(pad=0.3, rect=[0, 0, 1, 0.95])
401 | self.current_fig_2 = fig_2
402 | plt.figure(num=1)
403 | plt.draw()
404 | plt.figure(num=2).canvas.manager.window.move(500,0)
405 | plt.draw()
406 | except TypeError:
407 | pass
408 |
409 | def run_calculation(self):
410 | """ Applies the currently specified filters to the currently specified target csv file. """
411 |
412 | ## assert self.target_filepath, "You must provide a csv file before calling run."
413 | if not self.target_filepath:
414 | warnings = "You must provide an input file (e.g. *.csv, *.txt, *.aif) before calling run. Press \"Clear\" and try again. Please refer to the \"Hints\" box for a quick guide."
415 | information = ""
416 | self.show_dialog(warnings, information)
417 | return
418 |
419 | use_rouq1 = self.rouq1_tick.isChecked()
420 | use_rouq2 = self.rouq2_tick.isChecked()
421 | use_rouq3 = self.rouq3_tick.isChecked()
422 | use_rouq4 = self.rouq4_tick.isChecked()
423 | use_rouq5 = self.rouq5_tick.isChecked()
424 | min_num_pts = int(self.min_points_edit.text())
425 | min_r2 = float(self.minr2_edit.text())
426 | max_perc_error = float(self.rouq4_edit.text())
427 | adsorbate = self.adsorbate_combo_box.currentText()
428 | cross_sectional_area = float(self.adsorbate_cross_section_edit.text())
429 | molar_volume = float(self.adsorbate_molar_volume_edit.text())
430 |
431 | ## New lines or modifications made
432 | # Retrieve the BETSI results object if non-existent
433 | if self.bet_object is None:
434 | pressure, q_adsorbed, comments_to_data = get_data(input_file=self.target_filepath)
435 | if len(pressure) == 0 or len(q_adsorbed) == 0:
436 | warnings = "You must provide a valid input file. BETSI cannot read it. Press \"Clear\" and try again with a valid one."
437 | information = ""
438 | self.show_dialog(warnings, information)
439 | return
440 | self.bet_object = BETResult(pressure, q_adsorbed)
441 | self.bet_object.comments_to_data = comments_to_data
442 | self.bet_object.original_pressure_data = pressure
443 | self.bet_object.original_q_adsorbed_data = q_adsorbed
444 |
445 |
446 | # Apply the currently selected filters.
447 | self.bet_filter_result = BETFilterAppliedResults(self.bet_object,
448 | min_num_pts=min_num_pts,
449 | min_r2=min_r2,
450 | use_rouq1=use_rouq1,
451 | use_rouq2=use_rouq2,
452 | use_rouq3=use_rouq3,
453 | use_rouq4=use_rouq4,
454 | use_rouq5=use_rouq5,
455 | max_perc_error=max_perc_error,
456 | adsorbate=adsorbate,
457 | cross_sectional_area=cross_sectional_area,
458 | molar_volume=molar_volume)
459 |
460 | ## Adds interpolated points to adsorption data if no valid area was found by the original data
461 | if not self.bet_filter_result.has_valid_areas:
462 | iter_num = 0
463 | self.bet_object.comments_to_data['interpolated_points_added'] = True
464 | while (not self.bet_filter_result.has_valid_areas) and (iter_num < 3):
465 | print('Adding extra interpolated points to the data')
466 |
467 | pressure = self.bet_object.pressure
468 | q_adsorbed = self.bet_object.q_adsorbed
469 | comments_to_data = self.bet_object.comments_to_data
470 | interpolated_points_added = self.bet_object.comments_to_data['interpolated_points_added']
471 | original_pressure_data = self.bet_object.original_pressure_data
472 | original_q_adsorbed_data = self.bet_object.original_q_adsorbed_data
473 |
474 | self.bet_object = None
475 | self.bet_filter_result = None
476 | pressure_added_points, q_adsorbed_added_points = isotherm_pchip_reconstruction(pressure, q_adsorbed)
477 | self.bet_object = BETResult(pressure_added_points, q_adsorbed_added_points)
478 | self.bet_object.comments_to_data = comments_to_data
479 | self.bet_object.comments_to_data['interpolated_points_added'] = interpolated_points_added
480 | self.bet_object.original_pressure_data = original_pressure_data
481 | self.bet_object.original_q_adsorbed_data = original_q_adsorbed_data
482 |
483 | # Apply the currently selected filters.
484 | self.bet_filter_result = BETFilterAppliedResults(self.bet_object,
485 | min_num_pts=min_num_pts,
486 | min_r2=min_r2,
487 | use_rouq1=use_rouq1,
488 | use_rouq2=use_rouq2,
489 | use_rouq3=use_rouq3,
490 | use_rouq4=use_rouq4,
491 | use_rouq5=use_rouq5,
492 | max_perc_error=max_perc_error,
493 | adsorbate=adsorbate,
494 | cross_sectional_area=cross_sectional_area,
495 | molar_volume=molar_volume)
496 | iter_num += 1
497 |
498 | ## Warnings for pop-up message box
499 | warnings = ""
500 | information = ""
501 | if self.bet_object.comments_to_data['has_negative_pressure_points']:
502 | warnings += "- Imported adsorption data has negative pressure point(s)!\n"
503 | information += "- Negative pressure point(s) have been removed from the data.\n"
504 | if not self.bet_object.comments_to_data['monotonically_increasing_pressure']:
505 | warnings += "- The pressure points are not monotonically increasing!\n"
506 | information += "- Non-monotonically increasing pressure point(s) have been removed from the data.\n"
507 | if not self.bet_object.comments_to_data['rel_pressure_between_0_and_1']:
508 | warnings += "- The relative pressure values must lie between 0 and 1!\n"
509 | information += "- Relative pressure point(s) above 1.0 have been removed from the data.\n"
510 | if self.bet_object.comments_to_data['interpolated_points_added']:
511 | warnings += "- No valid areas found with the chosen minimum number of points! So, interpolated points are added to the data!\n"
512 |
513 | if self.bet_filter_result.has_valid_areas:
514 | self.plot_bet()
515 | if warnings != "":
516 | warnings = "Consider the following warning(s):\n" + warnings
517 | if information != "":
518 | information = "Note(s):\n" + information
519 | self.show_dialog(warnings, information)
520 | else:
521 | if warnings == "":
522 | warnings = "No valid areas found! Try again with a new set of data and/or change your criteria"
523 | self.show_dialog(warnings, information)
524 | else:
525 | warnings_1 = "No valid areas found! Try again with a new set of data and/or change your criteria.\n"
526 | warnings_2 = "Consider the following warning(s):\n"
527 | warnings = warnings_1 + warnings_2 + warnings
528 | information = "Note(s):\n" + information
529 | self.show_dialog(warnings, information)
530 |
531 | ## New lines addedd
532 | def show_dialog(self, warnings, information):
533 | dialog = QMessageBox()
534 | dialog.setText(warnings)
535 | dialog.setWindowTitle('Warnings')
536 | if warnings.find("No valid areas found!") != -1:
537 | dialog.setIcon(QMessageBox().Critical)
538 | dialog.setStandardButtons(QMessageBox.Ok)
539 | dialog.addButton("Clear", QMessageBox.AcceptRole)
540 | if information != "":
541 | 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"
542 | else:
543 | information = "Press \"Clear\" if you want to clear input data, reset to default values and start over with a new set of data.\n"
544 | elif warnings.find("You must provide a") != -1:
545 | dialog.setIcon(QMessageBox().Critical)
546 | dialog.addButton("Clear", QMessageBox.AcceptRole)
547 | else:
548 | dialog.setIcon(QMessageBox().Warning)
549 | dialog.setInformativeText(information)
550 |
551 | dialog.buttonClicked.connect(self.dialog_clicked)
552 | dialog.exec_()
553 |
554 | def dialog_clicked(self, dialog_button):
555 | if dialog_button.text() == "Clear":
556 | self.clear()
557 |
558 | def point_picker(self, event):
559 | line = event.artist
560 | picked_coords = line.get_offsets()[event.ind][0]
561 | # redefine min_i and min_j
562 | self.bet_filter_result.find_nearest_idx(picked_coords)
563 | # replot based on the new min_i and min_j
564 | self.plot_bet()
565 |
566 | def export(self):
567 | """ Print out the plot, filter config and results to the output directory. """
568 | if self.bet_filter_result is not None:
569 |
570 | # Create a local sub-directory for export.
571 | target_path = Path(self.target_filepath)
572 | output_subdir = Path(self.output_dir) / target_path.name
573 | output_subdir.mkdir(exist_ok=True)
574 |
575 | self.bet_filter_result.export(output_subdir)
576 |
577 | self.current_fig.savefig(str(output_subdir / f'{target_path.stem}_plot.pdf'), bbox_inches='tight')
578 | plt.show()
579 | self.current_fig_2.savefig(str(output_subdir / f'{target_path.stem}_RD_plots.pdf'))
580 | plt.show()
581 |
582 | def analyse_directory(self, dir_path):
583 | """ Run BETSI on all csv files within dir_path. Use current filter config."""
584 | use_rouq1 = self.rouq1_tick.isChecked()
585 | use_rouq2 = self.rouq2_tick.isChecked()
586 | use_rouq3 = self.rouq3_tick.isChecked()
587 | use_rouq4 = self.rouq4_tick.isChecked()
588 | use_rouq5 = self.rouq5_tick.isChecked()
589 | min_num_points = int(self.min_points_edit.text())
590 | min_r2 = float(self.minr2_edit.text())
591 | max_perc_error = float(self.rouq4_edit.text())
592 |
593 | adsorbate = self.adsorbate_combo_box.currentText()
594 | cross_sectional_area = float(self.adsorbate_cross_section_edit.text())
595 | molar_volume = float(self.adsorbate_molar_volume_edit.text())
596 |
597 | ## New lines added
598 | ##csv_paths = Path(dir_path).glob('*.csv')
599 | csv_paths = Path(dir_path).glob('*.csv')
600 | aif_paths = Path(dir_path).glob('*.aif')
601 | txt_paths = Path(dir_path).glob('*.txt')
602 | input_file_paths = (*csv_paths, *aif_paths, txt_paths)
603 |
604 | ##for file_path in csv_paths:
605 | for file_path in input_file_paths:
606 | # Update the table with current file
607 | self.populate_table(csv_path=str(file_path))
608 |
609 | # Run the analysis
610 | analyse_file(input_file=str(file_path),
611 | output_dir=self.output_dir,
612 | min_num_pts=min_num_points,
613 | min_r2=min_r2,
614 | use_rouq1=use_rouq1,
615 | use_rouq2=use_rouq2,
616 | use_rouq3=use_rouq3,
617 | use_rouq4=use_rouq4,
618 | use_rouq5=use_rouq5,
619 | max_perc_error=max_perc_error,
620 | adsorbate=adsorbate,
621 | cross_sectional_area=cross_sectional_area,
622 | molar_volume=molar_volume)
623 |
624 | def set_defaults(self):
625 | """Sets the widget to default state
626 | """
627 | # set default values for the tick marks
628 | self.rouq1_tick.setCheckState(True)
629 | self.rouq2_tick.setCheckState(True)
630 | self.rouq3_tick.setCheckState(True)
631 | self.rouq4_tick.setCheckState(True)
632 | self.rouq5_tick.setCheckState(False)
633 |
634 | # the ticks can only be on or off - Not sure why I need to do this every time, but doesn't matter
635 | self.rouq1_tick.setTristate(False)
636 | self.rouq2_tick.setTristate(False)
637 | self.rouq3_tick.setTristate(False)
638 | self.rouq4_tick.setTristate(False)
639 | self.rouq5_tick.setTristate(False)
640 | # set defaults for text fields
641 | self.minr2_edit.setText('0.995')
642 | self.rouq4_edit.setText('20')
643 | self.min_points_edit.setText('10')
644 |
645 | ## New lines added
646 | ## set defaults for adsorbate related parameters
647 | self.adsorbate_combo_box.setCurrentIndex(0)
648 | #self.adsorbate_cross_section_edit.setText('0.0')
649 | #self.adsorbate_molar_volume_edit.setText('0.0')
650 | self.adsorbate_cross_section_edit.setText(str(cross_sectional_area[self.adsorbate_combo_box.currentText()] * 1.0E18))
651 | self.adsorbate_molar_volume_edit.setText(str(mol_vol[self.adsorbate_combo_box.currentText()]))
652 |
653 |
654 | self.line_edit_values_before = {"minr2_edit": self.minr2_edit.text(), \
655 | "rouq4_edit": self.rouq4_edit.text(), \
656 | "min_points_edit": self.min_points_edit.text(), \
657 | "adsorbate_cross_section_edit": self.adsorbate_cross_section_edit.text(), \
658 | "adsorbate_molar_volume_edit": self.adsorbate_molar_volume_edit.text()}
659 |
660 |
661 | # trigger the corresponding sliders
662 | self.rouq4_edit_changed()
663 | self.minr2_edit_changed()
664 | self.min_points_edit_changed()
665 | # check the compatibility
666 | self.check_rouq_compatibility()
667 |
668 | ## check the compatibility for adsorbate
669 | self.check_adsorbate_compatibility()
670 |
671 | # if there is a figure, replot
672 | self.plot_bet()
673 |
674 | def clear(self):
675 | """Closes all plots and removes all data from memory"""
676 | # remove the bet filter result from memory
677 | self.bet_filter_result = None
678 | self.bet_object = None
679 | # clear the table
680 | self.clean_table()
681 | # close the plot
682 | if self.current_fig is not None:
683 | plt.close(fig=self.current_fig)
684 | plt.close(fig=self.current_fig_2)
685 |
686 | ## New lines added here
687 | ##reset the parameters to defaults
688 | self.target_filepath = None
689 | self.current_targetpath_label.setText(
690 | f"Loaded File: {self.target_filepath}")
691 | self.set_defaults()
692 |
693 | def set_editable(self, state):
694 | if state:
695 | self.rouq1_tick.setEnabled(True)
696 | self.rouq2_tick.setEnabled(True)
697 | self.rouq3_tick.setEnabled(True)
698 | self.rouq4_tick.setEnabled(True)
699 | self.rouq5_tick.setEnabled(True)
700 | self.minr2_edit.setEnabled(True)
701 | self.rouq4_edit.setEnabled(True)
702 | self.min_points_edit.setEnabled(True)
703 | self.rouq4_slider.setEnabled(True)
704 | self.minr2_slider.setEnabled(True)
705 | self.min_points_slider.setEnabled(True)
706 | self.adsorbate_combo_box.setEnabled(True)
707 | self.adsorbate_cross_section_edit.setEnabled(True)
708 | self.adsorbate_molar_volume_edit.setEnabled(True)
709 | else:
710 | self.rouq1_tick.setEnabled(False)
711 | self.rouq2_tick.setEnabled(False)
712 | self.rouq3_tick.setEnabled(False)
713 | self.rouq4_tick.setEnabled(False)
714 | self.rouq5_tick.setEnabled(False)
715 | self.minr2_edit.setEnabled(False)
716 | self.rouq4_edit.setEnabled(False)
717 | self.min_points_edit.setEnabled(False)
718 | self.rouq4_slider.setEnabled(False)
719 | self.minr2_slider.setEnabled(False)
720 | self.min_points_slider.setEnabled(False)
721 | self.adsorbate_combo_box.setEnabled(False)
722 | self.adsorbate_cross_section_edit.setEnabled(False)
723 | self.adsorbate_molar_volume_edit.setEnabled(False)
724 |
725 | def check_rouq_compatibility(self):
726 | use_rouq1 = self.rouq1_tick.isChecked()
727 | use_rouq2 = self.rouq2_tick.isChecked()
728 | use_rouq3 = self.rouq3_tick.isChecked()
729 | use_rouq4 = self.rouq4_tick.isChecked()
730 | use_rouq5 = self.rouq5_tick.isChecked()
731 | if not (use_rouq3 or use_rouq4):
732 | self.rouq2_tick.setEnabled(True)
733 | else:
734 | self.rouq2_tick.setChecked(True)
735 | self.rouq2_tick.setEnabled(False)
736 |
737 | def check_adsorbate_compatibility(self):
738 | if self.adsorbate_combo_box.currentText() != "Custom":
739 | self.adsorbate_cross_section_edit.setEnabled(False)
740 | self.adsorbate_molar_volume_edit.setEnabled(False)
741 | self.adsorbate_cross_section_edit.setText("{0:0.3f}".format(cross_sectional_area[self.adsorbate_combo_box.currentText()] * 1.0E18))
742 | self.adsorbate_molar_volume_edit.setText("{0:0.3f}".format(mol_vol[self.adsorbate_combo_box.currentText()]))
743 | #self.adsorbate_cross_section_edit.setText("0.0")
744 | #self.adsorbate_molar_volume_edit.setText("0.0")
745 | else:
746 | self.adsorbate_cross_section_edit.setEnabled(True)
747 | self.adsorbate_molar_volume_edit.setEnabled(True)
748 |
749 | def populate_table(self, csv_path):
750 | self.results_table.setColumnCount(2)
751 | self.results_table.setRowCount(1)
752 | self.results_table.setHorizontalHeaderLabels(
753 | ['Relative pressure (p/p\u2080)', 'Quantity adsorbed (cm\u00B3/g)'])
754 | self.results_table.setColumnWidth(0, 250)
755 | self.results_table.setColumnWidth(1, 250)
756 |
757 | # Change the box title
758 | self.current_targetpath_label.setText(
759 | f"Loaded File: {self.target_filepath}")
760 |
761 | if csv_path is not None and Path(csv_path).exists():
762 | pressure, q_adsorbed, comments_to_data = get_data(input_file=csv_path)
763 | self.results_table.setRowCount(len(pressure))
764 | for i in range(len(pressure)):
765 | self.results_table.setItem(
766 | i, 0, QTableWidgetItem(str(pressure[i])))
767 | self.results_table.setItem(
768 | i, 1, QTableWidgetItem(str(q_adsorbed[i])))
769 |
770 | def clean_table(self):
771 | """Cleans the table"""
772 | self.results_table.setColumnCount(2)
773 | self.results_table.setRowCount(0)
774 | self.results_table.setHorizontalHeaderLabels(
775 | ['Relative pressure (p/p\u2080)', 'Quantity adsorbed (cm\u00B3/g)'])
776 | self.results_table.setColumnWidth(0, 250)
777 | self.results_table.setColumnWidth(1, 250)
778 |
779 |
780 | def minr2_edit_changed(self):
781 | value = self.minr2_edit.text()
782 | mn = self.minr2_slider.minimum()
783 | mx = self.minr2_slider.maximum()
784 | try:
785 | value = int(round(float(value) * 1000))
786 | if value < mn:
787 | value = mn
788 | self.minr2_edit.setText(str(value / 1000))
789 | elif value > mx:
790 | value = mx
791 | self.minr2_edit.setText(str(value / 1000))
792 | self.minr2_slider.setValue(value)
793 | except (ValueError, TypeError):
794 | self.minr2_edit.setText('0.995')
795 | if self.line_edit_values_before["minr2_edit"] != self.minr2_edit.text():
796 | self.line_edit_values_before["minr2_edit"] = self.minr2_edit.text()
797 | self.maybe_run_calculation()
798 |
799 | def rouq4_edit_changed(self):
800 | value = self.rouq4_edit.text()
801 | mn = self.rouq4_slider.minimum()
802 | mx = self.rouq4_slider.maximum()
803 | try:
804 | value = int(float(value))
805 | if value < mn:
806 | value = mn
807 | self.rouq4_edit.setText(str(value))
808 | if value > mx:
809 | value = mx
810 | self.rouq4_edit.setText(str(value))
811 | self.rouq4_slider.setValue(value)
812 | except (ValueError, TypeError):
813 | self.rouq4_edit.setText('20')
814 | if self.line_edit_values_before["rouq4_edit"] != self.rouq4_edit.text():
815 | self.line_edit_values_before["rouq4_edit"] = self.rouq4_edit.text()
816 | self.maybe_run_calculation()
817 |
818 | def min_points_edit_changed(self):
819 | value = self.min_points_edit.text()
820 | mn = self.min_points_slider.minimum()
821 | mx = self.min_points_slider.maximum()
822 | try:
823 | value = int(round(float(value)))
824 | if value < mn:
825 | value = mn
826 | self.min_points_edit.setText(str(value))
827 | if value > mx:
828 | value = mx
829 | self.min_points_edit.setText(str(value))
830 | self.min_points_slider.setValue(value)
831 | except (ValueError, TypeError):
832 | self.min_points_edit.setText('10')
833 | if self.line_edit_values_before["min_points_edit"] != self.min_points_edit.text():
834 | self.line_edit_values_before["min_points_edit"] = self.min_points_edit.text()
835 | self.maybe_run_calculation()
836 |
837 | def rouq4_slider_changed(self):
838 | value = self.rouq4_slider.value()
839 | self.rouq4_edit.setText(str(value))
840 | self.maybe_run_calculation()
841 |
842 | def minr2_slider_changed(self):
843 | value = self.minr2_slider.value()
844 | self.minr2_edit.setText(str(value / 1000))
845 | self.maybe_run_calculation()
846 |
847 | def min_points_slider_changed(self):
848 | value = self.min_points_slider.value()
849 | self.min_points_edit.setText(str(value))
850 | self.maybe_run_calculation()
851 |
852 | def adsorbate_related_edit_changed(self):
853 | if not self.is_float(self.adsorbate_cross_section_edit.text()):
854 | self.adsorbate_cross_section_edit.setText("0.0")
855 | if not self.is_float(self.adsorbate_molar_volume_edit.text()):
856 | self.adsorbate_molar_volume_edit.setText("0.0")
857 | if self.line_edit_values_before["adsorbate_cross_section_edit"] != self.adsorbate_cross_section_edit.text() or \
858 | self.line_edit_values_before["adsorbate_molar_volume_edit"] != self.adsorbate_molar_volume_edit.text():
859 | self.line_edit_values_before["adsorbate_cross_section_edit"] = self.adsorbate_cross_section_edit.text()
860 | self.line_edit_values_before["adsorbate_molar_volume_edit"] = self.adsorbate_molar_volume_edit.text()
861 | self.maybe_run_calculation()
862 |
863 | def adsorbate_combo_box_changed(self):
864 | if self.adsorbate_combo_box.currentText() == "Custom":
865 | self.adsorbate_cross_section_edit.setText("0.0")
866 | self.adsorbate_molar_volume_edit.setText("0.0")
867 | self.maybe_run_calculation()
868 |
869 | def line_edit_changed(self):
870 | # modify_check = []
871 | # modify_check.append(self.minr2_edit.isModified())
872 | # modify_check.append(self.rouq4_edit.isModified())
873 | # modify_check.append(self.min_points_edit.isModified())
874 | # modify_check.append(self.adsorbate_cross_section_edit.isModified())
875 | # modify_check.append(self.adsorbate_molar_volume_edit.isModified())
876 | current_line_edit_values = self.get_line_edit_current_values()
877 | if self.line_edit_values_before != current_line_edit_values:
878 | self.maybe_run_calculation()
879 | self.line_edit_values_before = current_line_edit_values
880 |
881 | def get_line_edit_current_values(self):
882 | current_line_edit_values = []
883 | current_line_edit_values.append(self.minr2_edit.text())
884 | current_line_edit_values.append(self.rouq4_edit.text())
885 | current_line_edit_values.append(self.min_points_edit.text())
886 | current_line_edit_values.append(self.adsorbate_cross_section_edit.text())
887 | current_line_edit_values.append(self.adsorbate_molar_volume_edit.text())
888 | return current_line_edit_values
889 |
890 | def is_float(self, string):
891 | try:
892 | float(string)
893 | return True
894 | except ValueError:
895 | return False
896 |
897 | def __del__(self):
898 | try:
899 | plt.close(self.current_fig_2)
900 | plt.close(self.current_fig)
901 | except:
902 | pass
903 |
904 | class OutputCanvas(FigureCanvas):
905 | def __init__(self, parent, dpi=100):
906 | self.fig = Figure(dpi=dpi)
907 | self.fig.subplots_adjust(left=0.05, right=0.95)
908 | FigureCanvas.__init__(self, self.fig)
909 | self.setParent(parent)
910 | FigureCanvas.setSizePolicy(self,
911 | QSizePolicy.Expanding,
912 | QSizePolicy.Expanding)
913 | FigureCanvas.updateGeometry(self)
914 |
915 | class OutputCanvas_2(FigureCanvas):
916 | def __init__(self,parent,dpi=100):
917 | self.fig_2 = Figure(dpi=dpi)
918 | self.fig_2.subplots_adjust(left=.05, right=.95)
919 | FigureCanvas.__init__(self,self.fig_2)
920 | self.setParent(parent)
921 | FigureCanvas.setSizePolicy(self,
922 | QSizePolicy.Expanding,
923 | QSizePolicy.Expanding)
924 | FigureCanvas.updateGeometry(self)
925 |
926 | class OutLog(logging.Handler):
927 | def __init__(self, out_widget):
928 | """(edit, out=None, color=None) -> can write stdout, stderr to a
929 | QTextEdit.
930 | """
931 | logging.Handler.__init__(self)
932 | self.out_widget = out_widget
933 |
934 | def emit(self, message):
935 | self.write(message.getMessage() + '\n')
936 |
937 | def write(self, m):
938 | self.out_widget.moveCursor(QtGui.QTextCursor.End)
939 | self.out_widget.insertPlainText(m)
940 |
941 |
942 | def runbetsi():
943 | QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
944 | app = QApplication(sys.argv)
945 | ex = BETSI_gui()
946 | ex.show()
947 | sys.exit(app.exec_())
948 |
949 | runbetsi()
--------------------------------------------------------------------------------
/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 |
182 | class BETFilterAppliedResults:
183 | """
184 | Structure obtained from applying a set of custom filters to a BETResult.
185 |
186 | After initialisation with an initialised BETResult object and set of desired features, the
187 | BETFilterAppliedResults object contains all data required to produce any plot.
188 | """
189 |
190 | def __init__(self, bet_result, **kwargs):
191 |
192 | # Transfer all the properties from the original BET calculation
193 | self.__dict__.update(bet_result.__dict__)
194 | self.filter_params = kwargs
195 |
196 | # Apply the selected filters in turn
197 | filter_mask = np.ones_like(bet_result.c)
198 |
199 | if kwargs.get('use_rouq1', True):
200 | filter_mask = filter_mask * bet_result.rouq1
201 |
202 | if kwargs.get('use_rouq2', True):
203 | filter_mask = filter_mask * bet_result.rouq2
204 |
205 | if kwargs.get('use_rouq3', True):
206 | filter_mask = filter_mask * bet_result.rouq3
207 |
208 | if kwargs.get('use_rouq4', True):
209 | max_perc_error = kwargs.get('max_perc_error', 20)
210 | filter_mask = filter_mask * (bet_result.pc_error < max_perc_error)
211 |
212 | if kwargs.get('use_rouq5', False):
213 | filter_mask = filter_mask * bet_result.rouq5
214 |
215 | adsorbate = kwargs.get('adsorbate', "N2")
216 |
217 | if adsorbate == "Custom":
218 | cross_sectional_area[adsorbate] = 1.0E-18 * kwargs.get('cross_sectional_area')
219 | mol_vol[adsorbate] = kwargs.get('molar_volume')
220 |
221 | # Filter results that have less than the minimum points
222 | min_points = kwargs.get('min_num_pts', 10)
223 | filter_mask = filter_mask * (bet_result.point_count > min_points)
224 |
225 | # Block out results that have less than the minimum R2
226 | min_r2 = kwargs.get('min_r2', 0.9)
227 | filter_mask = filter_mask * (bet_result.fit_rsquared > min_r2)
228 |
229 | ## assert np.sum(filter_mask) != 0, "NO valid areas found"
230 | self.has_valid_areas = False
231 | self.original_pressure_data = bet_result.original_pressure_data
232 | self.original_q_adsorbed_data = bet_result.original_q_adsorbed_data
233 | self.comments_to_data = bet_result.comments_to_data
234 | self.adsorbate = adsorbate
235 |
236 | if np.sum(filter_mask) != 0:
237 | self.has_valid_areas = True
238 |
239 | # Compute valid BET areas
240 | ##self.bet_areas = NITROGEN_RADIUS * AVOGADRO_N * \
241 | ## NITROGEN_MOL_VOL * bet_result.nm * 0.000001
242 | self.bet_areas = cross_sectional_area[adsorbate] * AVOGADRO_N * \
243 | mol_vol[adsorbate] * bet_result.nm * 0.000001
244 | self.bet_areas_filtered = self.bet_areas * filter_mask
245 | self.valid_indices = np.where(self.bet_areas_filtered > 0)
246 |
247 | # Define isotherm knee as ending on highest P
248 | self.list = np.where(self.valid_indices[1] == np.amax(self.valid_indices[1]))
249 | self.lower = (self.valid_indices[0])[self.list]
250 | self.upper = (self.valid_indices[1])[self.list]
251 | self.valid_knee_indices = (self.lower, self.upper)
252 | self.knee_only_bet_areas_filtered = self.bet_areas * bet_result.rouq5
253 |
254 |
255 | # Define the valid cases
256 | self.num_valid = len(self.valid_indices[0])
257 | self.valid_bet_areas = self.bet_areas[self.valid_indices]
258 | self.valid_pc_errors = bet_result.pc_error[self.valid_indices]
259 | self.valid_knee_bet_areas = self.bet_areas[self.valid_knee_indices]
260 | self.valid_knee_pc_errors = bet_result.pc_error[self.valid_knee_indices]
261 | self.valid_calc_pressures = bet_result.calc_pressure[self.valid_indices]
262 | self.valid_nm = bet_result.nm[self.valid_indices]
263 |
264 | ## New lines added
265 | ## was needed in the plotting.py file
266 | self.pc_errors = bet_result.pc_error
267 |
268 | # Find min error and corresponding indices
269 | knee_only_filter = np.zeros([len(bet_result.pressure),len(bet_result.pressure)])
270 | knee_only_filter[self.valid_knee_indices] = 1
271 | knee_filter = filter_mask * knee_only_filter
272 | filtered_pcerrors = bet_result.pc_error + 1000.0 * (1 - filter_mask)
273 | knee_filtered_pcerrors = bet_result.pc_error + \
274 | 1000.0 * (1 - knee_filter)
275 | min_i, min_j = np.unravel_index(
276 | np.argmin(knee_filtered_pcerrors), filtered_pcerrors.shape)
277 | self.min_i = min_i
278 | self.min_j = min_j
279 |
280 | self.compute_BET_curve()
281 |
282 | self.std_area = np.std(self.bet_areas[self.valid_indices])
283 |
284 | def compute_BET_curve(self):
285 | """Function for computing BET curve. This is separated to a different function to allow custom min_i min_j"""
286 |
287 | # Compute BET curve at min point
288 | numerator = (self.nm[self.min_i, self.min_j] *
289 | self.c[self.min_i, self.min_j] * self.x_range)
290 | denominator = (1. - self.x_range) + (1. - self.x_range)\
291 | * (self.c[self.min_i, self.min_j] - 1.) * self.x_range
292 |
293 | with np.errstate(divide='ignore'):
294 | self.bet_curve = numerator / denominator
295 |
296 | self.min_area = self.bet_areas[self.min_i, self.min_j]
297 |
298 | def find_nearest_idx(self, coords):
299 | """Finds min_i and min_j for the given monlayer error coordinates
300 | """
301 | # find the index bet areas
302 | betarea_idx = np.abs(self.valid_bet_areas - coords[0]).argmin()
303 | pcerror_idx = np.abs(self.valid_pc_errors - coords[1]).argmin()
304 |
305 | # get the indices
306 | min_i = self.valid_indices[0][betarea_idx] #+ 1
307 | min_j = self.valid_indices[1][pcerror_idx] #+ 1
308 |
309 | self.min_i = min_i
310 | self.min_j = min_j
311 |
312 | self.compute_BET_curve()
313 |
314 | def export(self, filepath):
315 | """ Write all relevant information to the directory at filepath.
316 |
317 | """
318 | filepath = Path(filepath)
319 |
320 | # Write out the filter settings used to get these results.
321 | with (filepath / 'filter_summary.json').open('w') as fp:
322 | pprint(self.filter_params, fp)
323 |
324 | # Write out the key results.
325 | with (filepath / 'results.txt').open('w') as fp:
326 | print(f"Best area has: ", file=fp)
327 | print(f"Area: {self.min_area} ", file=fp)
328 | print(
329 | f"Total points: {self.point_count[self.min_i, self.min_j]} ", file=fp)
330 | print(
331 | f"R-Squared: {self.fit_rsquared[self.min_i, self.min_j]} ", file=fp)
332 | print(
333 | f"Linear Gradient: {self.fit_grad[self.min_i, self.min_j]} ", file=fp)
334 | print(
335 | f"Intercept: {self.fit_intercept[self.min_i, self.min_j]} ", file=fp)
336 | print(
337 | f"C: {self.c[self.min_i, self.min_j]} ", file=fp)
338 | print(
339 | f"Monolayer Loading: {self.nm[self.min_i, self.min_j]} ", file=fp)
340 | print(
341 | f"Calculated Pressure: {self.calc_pressure[self.min_i, self.min_j]} ", file=fp)
342 | print(
343 | f"Read pressure: {self.corresponding_pressure_pchip[self.min_i,self.min_j]} ", file =fp)
344 | print(
345 | f"Error: {self.error[self.min_i, self.min_j]} ", file=fp)
346 |
347 | # Write out a set of csv files
348 | matrices_f = filepath / 'matrices'
349 | matrices_f.mkdir(exist_ok=True)
350 | np.savetxt(str(matrices_f / 'point_counts.csv'),
351 | self.point_count, delimiter=',', fmt='%i')
352 | np.savetxt(str(matrices_f / 'rouq1.csv'),
353 | self.rouq1, delimiter=',', fmt='%i')
354 | np.savetxt(str(matrices_f / 'rouq2.csv'),
355 | self.rouq2, delimiter=',', fmt='%i')
356 | np.savetxt(str(matrices_f / 'rouq3.csv'),
357 | self.rouq3, delimiter=',', fmt='%i')
358 | np.savetxt(str(matrices_f / 'rouq4.csv'),
359 | self.rouq4, delimiter=',', fmt='%i')
360 | np.savetxt(str(matrices_f / 'rouq5.csv'),
361 | self.rouq5, delimiter=',', fmt='%i')
362 | np.savetxt(str(matrices_f / 'bet_areas_filtered.csv'),
363 | self.bet_areas_filtered, delimiter=',', fmt='%1.3f')
364 | np.savetxt(str(matrices_f / 'bet_areas.csv'),
365 | self.bet_areas, delimiter=',', fmt='%1.3f')
366 | np.savetxt(str(matrices_f / 'fit_rsquared.csv'),
367 | self.fit_rsquared, delimiter=',', fmt='%1.3f')
368 | np.savetxt(str(matrices_f / 'fit_grad.csv'),
369 | self.fit_grad, delimiter=',', fmt='%1.3f')
370 | np.savetxt(str(matrices_f / 'fit_intercept.csv'),
371 | self.fit_intercept, delimiter=',', fmt='%1.3f')
372 | np.savetxt(str(matrices_f / 'c_value.csv'),
373 | self.c, delimiter=',', fmt='%1.3f')
374 | np.savetxt(str(matrices_f / 'nm.csv'),
375 | self.nm, delimiter=',', fmt='%1.3f')
376 | np.savetxt(str(matrices_f / 'calc_pressure.csv'),
377 | self.calc_pressure, delimiter=',', fmt='%1.3f')
378 | np.savetxt(str(matrices_f / 'error.csv'),
379 | self.error, delimiter=',', fmt='%1.3f')
380 | np.savetxt(str(matrices_f / 'pc_error.csv'),
381 | self.pc_error, delimiter=',', fmt='%1.3f')
382 |
383 |
384 | def analyse_file(input_file, output_dir=None, **kwargs):
385 | """ Entry point for performing BET analysis on a single named csv file.
386 | If the output directory does not exist, one is created automatically."""
387 |
388 | if output_dir is None:
389 | output_dir = Path(os.getcwd() + '/bet_output')
390 |
391 | if isinstance(input_file, str):
392 | input_file = Path(input_file)
393 |
394 | if isinstance(output_dir, str):
395 | output_dir = Path(output_dir)
396 |
397 | output_subdir = output_dir / input_file.name
398 | output_subdir.mkdir(exist_ok=True, parents=True)
399 |
400 | # Compute unfiltered results
401 | pressure, q_adsorbed, comments_to_data = get_data(input_file=input_file)
402 | betsi_unfiltered = BETResult(pressure, q_adsorbed)
403 |
404 | # Apply custom filters:
405 | betsi_filtered = BETFilterAppliedResults(betsi_unfiltered, **kwargs)
406 |
407 | # Export the results
408 | betsi_filtered.export(output_subdir)
409 |
410 | # Create and save a PDF plot
411 | fig = create_matrix_plot(betsi_filtered, name=input_file.stem)
412 |
413 | #fig.tight_layout(pad=0.3, rect=[0, 0, 1, 0.95])
414 | fig.savefig(
415 | str(output_subdir / f'{input_file.stem}_combined_plot.pdf'), bbox_inches='tight')
416 | #plt.tight_layout()
417 | plt.show()
418 |
419 | # Create and show Diagnostics plot
420 | fig_2 = regression_diagnostics_plots(betsi_filtered,name=input_file.stem)
421 | fig_2.tight_layout(pad=.3, rect=[0,0,1,.95])
422 | plt.show()
423 |
424 |
425 | if __name__ == "__main__":
426 | analyse_file(
427 | Path(r"/Users/johannesosterrieth/Desktop/q_nu1105.csv"))
428 |
--------------------------------------------------------------------------------
/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 | ## New lines added
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 | ## New lines added
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 | ## New lines added
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 | ## New lines added
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/utils.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | """
4 | from pathlib import Path
5 |
6 | import numpy as np
7 | from scipy.interpolate import splrep, PchipInterpolator, pchip_interpolate
8 |
9 |
10 | def get_data(input_file):
11 | """Read pressure and Nitrogen uptake data from file. Asserts pressures in units of bar.
12 |
13 | Args:
14 | input_file: Path the path to the input file
15 |
16 | Returns:
17 | Pressure and Quantity adsorbed.
18 | """
19 | input_file = Path(input_file)
20 |
21 | if str(input_file).find('.txt') != -1 or str(input_file).find('.aif') != -1:
22 | pressure, q_adsorbed = get_data_for_txt_aif(str(input_file))
23 | else:
24 | try:
25 | data = np.loadtxt(str(input_file), skiprows=0, delimiter=',')
26 | pressure = data[:, 0]
27 | q_adsorbed = data[:, 1]
28 | except ValueError:
29 | data = np.loadtxt(str(input_file), skiprows=1, delimiter=',')
30 | pressure = data[:, 0]
31 | q_adsorbed = data[:, 1]
32 |
33 | ## New lines added here
34 | comments_to_data = {'has_negative_pressure_points': False,\
35 | 'monotonically_increasing_pressure': True,\
36 | 'rel_pressure_between_0_and_1': True}
37 | ## removes negetive relative pressure points if any
38 | negative_pressure_indexes = np.where(pressure < 0)[0]
39 | if len(negative_pressure_indexes) > 0:
40 | comments_to_data['has_negative_pressure_points'] = True
41 | pressure = np.delete(pressure, negative_pressure_indexes)
42 | q_adsorbed = np.delete(q_adsorbed, negative_pressure_indexes)
43 |
44 | ## checks if relative pressure points are monotonically increasing, if not,
45 | ## removes problematic points
46 | if not (pressure == np.sort(pressure)).all():
47 | comments_to_data['monotonically_increasing_pressure'] = False
48 | temp_index = 0
49 | temp_pressure = pressure
50 | temp_q_adsorbed = q_adsorbed
51 | while temp_index < len(temp_pressure)-1:
52 | if temp_pressure[temp_index+1] <= temp_pressure[temp_index]:
53 | temp_pressure = np.delete(temp_pressure, [temp_index+1])
54 | temp_q_adsorbed = np.delete(temp_q_adsorbed, [temp_index+1])
55 | else:
56 | temp_index += 1
57 | pressure = temp_pressure
58 | q_adsorbed = temp_q_adsorbed
59 |
60 | ## checks if relative pressure points lie between 0 and 1 (bar)
61 | if not (pressure < 1.1).all():
62 | comments_to_data['rel_pressure_between_0_and_1'] = False
63 | pressure_above_one_indexes = np.where(pressure > 1.1)[0]
64 | pressure = np.delete(pressure, pressure_above_one_indexes)
65 | q_adsorbed = np.delete(q_adsorbed, pressure_above_one_indexes)
66 |
67 | comments_to_data['interpolated_points_added'] = False
68 |
69 | ## assert (pressure < 1.1).all(), "Relative pressure must lie between 0 and 1 bar."
70 | return pressure, q_adsorbed, comments_to_data
71 |
72 |
73 | def get_fitted_spline(pressure, q_adsorbed):
74 | """ Fits a cubic spline to the isotherm.
75 |
76 | Args:
77 | pressure: Array of relative pressure values.
78 | q_adsorbed: Array of Nitrogen uptake values.
79 |
80 | Returns:
81 | tck tuple of spline parameters.
82 | """
83 | return splrep(pressure, q_adsorbed, s=50, k=3, quiet=True)
84 |
85 | def get_pchip_interpolation(pressure,q_adsorbed):
86 | """ Fits isotherm with shape preserving pchip interpolation
87 |
88 | Args:
89 | pressure: Array of relative pressure values
90 | q_adsorbed: Array of Nitrogen uptake values
91 |
92 | Returns:
93 | Pchip parameters
94 | """
95 | return PchipInterpolator(pressure,q_adsorbed, axis =0, extrapolate=None)
96 |
97 | def isotherm_pchip_reconstruction(pressure, q_adsorbed):
98 | """ Fits isotherm with a pchip interpolation. Can use this to
99 | calculate BET area for difficult isotherms
100 |
101 | Args: pressure: Array of relative pressure values
102 | q_adsorbed: Array of Nitrogen uptake values
103 |
104 | Returns: Array of interpolated nitrogen uptake values
105 |
106 | """
107 | ## x_range = np.linspace(pressure[0], pressure[len(pressure) -1 ], 500)
108 | ## y_pchip = pchip_interpolate(pressure,q_adsorbed,x_range, der=0, axis=0)
109 |
110 | ## pressure = x_range
111 | ## q_adsorbed = y_pchip
112 |
113 | ## New lines added from here
114 | ## Add new interpolated points using pchip interpolation while having the original data points in the list as well
115 | x_range = np.linspace(np.log10(pressure[0]), np.log10(pressure[len(pressure) -1 ]), len(pressure))
116 | delta_x = abs(x_range[1] - x_range[0])/2
117 | for p in np.log10(pressure[1:-1]):
118 | to_be_deleted_indexes = []
119 | index = np.searchsorted(x_range,p)
120 | if 0 <= index < len(x_range) and abs(p - x_range[index]) < delta_x:
121 | to_be_deleted_indexes.append(index)
122 | if 0 <= (index+1) < len(x_range) and abs(p - x_range[index+1]) < delta_x:
123 | to_be_deleted_indexes.append(index+1)
124 | if 0 <= (index-1) < len(x_range) and abs(p - x_range[index-1]) < delta_x:
125 | to_be_deleted_indexes.append(index-1)
126 | x_range = np.delete(x_range, to_be_deleted_indexes)
127 | x_range = np.append(x_range[1:-1], np.log10(pressure))
128 | x_range.sort()
129 | x_range = np.power(10, x_range)
130 |
131 | y_pchip = pchip_interpolate(pressure,q_adsorbed,x_range, der=0, axis=0)
132 |
133 | pressure_new = x_range
134 | q_adsorbed_new = y_pchip
135 |
136 | return pressure_new, q_adsorbed_new
137 |
138 | def get_data_for_txt_aif(input_file):
139 | """ Read pressure and Nitrogen uptake data from file if the file extension is *.txt or *.aif.
140 | this function will be called in the get_data() function, and should not be called alone anywhere in the code
141 | as it might cause some errors.
142 |
143 | Args:
144 | input_file: String of the path to the input file
145 |
146 | Returns:
147 | Pressure and Quantity adsorbed.
148 | """
149 |
150 | pressure = []
151 | q_adsorbed = []
152 |
153 | with open(input_file, 'r') as f:
154 | input_file_lines = f.readlines()
155 |
156 | if len(input_file_lines) == 0:
157 | pressure = np.array(pressure)
158 | q_adsorbed = np.array(q_adsorbed)
159 | return pressure, q_adsorbed
160 |
161 | index_start = 0
162 | index_stop = len(input_file_lines) - 1
163 | index_iter = 0
164 | first_loop_index = 0
165 |
166 | if input_file.find(".aif") != -1:
167 | for line in input_file_lines:
168 | if line.find("loop_") != -1 and first_loop_index == 0:
169 | first_loop_index = index_iter;
170 | if line.find("_adsorp_pressure") != -1:
171 | _adsorp_pressure_index = index_iter;
172 | if line.find("_adsorp_p0") != -1:
173 | _adsorp_p0_index = index_iter;
174 | if line.find("_adsorp_amount") != -1:
175 | index_start = index_iter
176 | if index_start > 0 and (line.find("loop_") != -1 or index_iter + 1 == len(input_file_lines)):
177 | index_stop = index_iter
178 | break
179 | index_iter += 1
180 | for index in range(index_start, index_stop + 1):
181 | val = []
182 | for t in input_file_lines[index].split():
183 | try:
184 | val.append(float(t))
185 | except ValueError:
186 | pass
187 | if len(val) >= 3:
188 | pressure.append(val[_adsorp_pressure_index - first_loop_index - 1]/val[_adsorp_p0_index - first_loop_index - 1])
189 | q_adsorbed.append(val[index_start - first_loop_index - 1])
190 | else:
191 | delimiter = " "
192 | if input_file_lines[round(len(input_file_lines)/2)].find(",") != -1:
193 | delimiter = ","
194 | elif input_file_lines[round(len(input_file_lines)/2)].find(" ") != -1:
195 | delimiter = " "
196 | elif input_file_lines[round(len(input_file_lines)/2)].find("\t") != -1:
197 | delimiter = "\t"
198 | for line in input_file_lines:
199 | val = []
200 | for t in line.split(delimiter):
201 | try:
202 | val.append(float(t))
203 | except ValueError:
204 | pass
205 | if len(val) >= 2:
206 | pressure.append(val[0])
207 | q_adsorbed.append(val[1])
208 | if len(pressure) == 0 or len(q_adsorbed) == 0:
209 | print("You must provide a valid input file!")
210 |
211 | pressure = np.array(pressure)
212 | q_adsorbed = np.array(q_adsorbed)
213 |
214 | return pressure, q_adsorbed
--------------------------------------------------------------------------------
/cli.py:
--------------------------------------------------------------------------------
1 | from betsi.gui import runbetsi
2 |
3 | runbetsi() # pylint: disable=no-value-for-parameter
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/docs/images/BETSI-logo.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/BETSI-logo.jpeg
--------------------------------------------------------------------------------
/docs/images/a2ml_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/a2ml_logo.png
--------------------------------------------------------------------------------
/docs/images/betsi_logo.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/betsi_logo.PNG
--------------------------------------------------------------------------------
/docs/images/executables-banner.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/executables-banner.PNG
--------------------------------------------------------------------------------
/docs/images/step-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/step-1.png
--------------------------------------------------------------------------------
/docs/images/step-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/step-10.png
--------------------------------------------------------------------------------
/docs/images/step-11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/step-11.png
--------------------------------------------------------------------------------
/docs/images/step-12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/step-12.png
--------------------------------------------------------------------------------
/docs/images/step-13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/step-13.png
--------------------------------------------------------------------------------
/docs/images/step-14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/step-14.png
--------------------------------------------------------------------------------
/docs/images/step-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/step-2.png
--------------------------------------------------------------------------------
/docs/images/step-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/step-3.png
--------------------------------------------------------------------------------
/docs/images/step-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/step-4.png
--------------------------------------------------------------------------------
/docs/images/step-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/step-5.png
--------------------------------------------------------------------------------
/docs/images/step-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/step-6.png
--------------------------------------------------------------------------------
/docs/images/step-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/step-7.png
--------------------------------------------------------------------------------
/docs/images/step-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/step-8.png
--------------------------------------------------------------------------------
/docs/images/step-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nakulrampal/betsi-gui/e68e3def588ef46116ce32677cdf51401f261701/docs/images/step-9.png
--------------------------------------------------------------------------------
/executables/BETSI_v1.1.0_windows.exe:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:3258183805b8a5c3e82b984e353d6614e8fb48c1ffef32f825193577e5812be9
3 | size 183088922
4 |
--------------------------------------------------------------------------------
/executables/mac_executables.zip:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:36e250cf9f445bdf2c3e7f76e0785f3e10fd16d76c90d78475b07844c2e6fae1
3 | size 144889483
4 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 | )
--------------------------------------------------------------------------------