├── .idea └── .gitignore ├── LICENSE ├── README.md ├── app-logo.key ├── app-logo.png ├── binder ├── postBuild ├── requirements.txt └── runtime.txt ├── buckinghampy-gui.ipynb ├── buckinghampy ├── __init__.py ├── buckinghampi.py └── buckinghampigui.py ├── doc ├── Gui-example.png ├── buckinghampi.m.html └── readme_result.png ├── examples.ipynb ├── generate-doc.sh ├── requirements.txt ├── setup.cfg └── setup.py /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mokbel Karam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BuckinghamPy 2 | 3 | ## Use our new web app! 4 | 5 | drawing 6 | (Launch GUI App) 7 | 8 | [Watch the youtube video](https://youtu.be/WGoOQh2MPHI) 9 | 10 |
11 |
12 | 13 | You can calso use the deprecated GUI interface via binder 14 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/saadgroup/BuckinghamPy/master?filepath=examples.ipynb) (Jupyter Notebook Examples) 15 | 16 | ## Description 17 | BuckinghamPy is a Python code that implements the Buckingham-Pi theorem and returns all possible sets of dimensionless groups. 18 | 19 | The methodology is discussed in the original artictle: https://www.sciencedirect.com/science/article/pii/S2352711021001291 20 | 21 | Cite as: Karam, M., & Saad, T. (2021). BuckinghamPy: A Python software for dimensional analysis. SoftwareX, 16, 100851. 22 | 23 | ## Installation 24 | --- 25 | Clone the package from the github repository into the current directory 26 | ```buildoutcfg 27 | git clone https://github.com/saadgroup/BuckinghamPy.git BuckinghamPy 28 | ``` 29 | Now change directory to the git repo 30 | ```buildoutcfg 31 | cd BuckinghamPy 32 | ``` 33 | Use `pip` to install the package in the active python evironment 34 | ```buildoutcfg 35 | pip install . 36 | ``` 37 | Note that last two steps - you must change directories to the repo directory and call `pip` from within it. 38 | ## Example 39 | 40 | Consider a fluid with density R and viscosity V, pumped in a centrifugal pump with power input P, a volume flow rate Q, an impeller diameter E, and a rotational rate G. 41 | 42 | The homogeneous function that relates all these variables is: f(R, V, P, Q, E, G) = 0 43 | 44 | Using the fundamental units (M, L, T), find all the sets of dimensionless terms with the power input P being part of only one dimensionless term per set. 45 | 46 | Using BuckinghamPy, we execute the following code: 47 | 48 | ```buildoutcfg 49 | from buckinghampy import BuckinghamPi 50 | 51 | Example = BuckinghamPi() 52 | Example.add_variable(name='R', dimensions='M/L^(3)') 53 | Example.add_variable(name='P', dimensions='M*L^(2)/(T^3)', non_repeating=True) 54 | Example.add_variable(name='V', dimensions='M/(T*L)') 55 | Example.add_variable(name='Q', dimensions='L^(3)/T') 56 | Example.add_variable(name='E', dimensions='L') 57 | Example.add_variable(name='G', dimensions='1/T') 58 | 59 | Example.generate_pi_terms() 60 | 61 | Example.print_all() 62 | ``` 63 | ![Latex Rendered Results](doc/readme_result.png) 64 | 65 | or you can import the graphic user interface only in a Jupyter cell 66 | ```buildoutcfg 67 | from buckinghampy import BuckinghamPiGui 68 | 69 | GUI=BuckinghamPiGui() 70 | ``` 71 | 72 | --- 73 | ## Note 74 | 75 | In order to speed up the calculations, the maximum number of sets per non-repeating variable is set to `20`. 76 | 77 | ``` 78 | self.__flagged_var_max_sets = 20 79 | ``` 80 | You could change this number to `-1` to get all the sets, `buckinghampy/buckinghampi.py line 43`. 81 | 82 | --- 83 | ## See Also 84 | 85 | * [Documentation](https://htmlpreview.github.io/?https://github.com/saadgroup/BuckinghamPy/blob/master/doc/buckinghampi.m.html) 86 | --- 87 | -------------------------------------------------------------------------------- /app-logo.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saadgroup/BuckinghamPy/1fa26797d8f74faff3af59011c11a661be1ff1e4/app-logo.key -------------------------------------------------------------------------------- /app-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saadgroup/BuckinghamPy/1fa26797d8f74faff3af59011c11a661be1ff1e4/app-logo.png -------------------------------------------------------------------------------- /binder/postBuild: -------------------------------------------------------------------------------- 1 | jupyter contrib nbextension install --user 2 | jupyter nbextension enable --py widgetsnbextension 3 | jupyter labextension install @jupyter-widgets/jupyterlab-manager@0.38 4 | jupyter nbextension enable init_cell/main 5 | jupyter trust examples.ipynb 6 | 7 | pip install . -------------------------------------------------------------------------------- /binder/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.16.2 2 | sympy>=1.3 3 | tabulate>=0.8.9 -------------------------------------------------------------------------------- /binder/runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.6.1 -------------------------------------------------------------------------------- /buckinghampy-gui.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# BuckinghamPy: A Software for Dimensional Analysis\n", 8 | "**Mokbel Karam and Tony Saad (www.tsaad.net)
Department of Chemical Engineering
University of Utah**\n", 9 | "
\n", 10 | "This notebook represents the graphical user interface of `BuckinghamPy`: www.github.com/saadgroup/buckinghampy" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "# Execute the cell below to lunch the GUI application (shift + enter)" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 1, 23 | "metadata": { 24 | "scrolled": false 25 | }, 26 | "outputs": [ 27 | { 28 | "data": { 29 | "application/vnd.jupyter.widget-view+json": { 30 | "model_id": "975dd7e523344f1095238b23efb6ea70", 31 | "version_major": 2, 32 | "version_minor": 0 33 | }, 34 | "text/plain": [ 35 | "HBox(children=(BoundedIntText(value=5, continuous_update=True, description='Number of Variables:', style=Descr…" 36 | ] 37 | }, 38 | "metadata": {}, 39 | "output_type": "display_data" 40 | }, 41 | { 42 | "data": { 43 | "application/vnd.jupyter.widget-view+json": { 44 | "model_id": "3c626da9f0ee4f8894ebaf51c6d4e108", 45 | "version_major": 2, 46 | "version_minor": 0 47 | }, 48 | "text/plain": [ 49 | "VBox(children=(Box(children=(Textarea(value='', description='Name:', layout=Layout(height='32px', width='auto'…" 50 | ] 51 | }, 52 | "metadata": {}, 53 | "output_type": "display_data" 54 | }, 55 | { 56 | "data": { 57 | "application/vnd.jupyter.widget-view+json": { 58 | "model_id": "f78f93512e704452a1cfe5fb26212552", 59 | "version_major": 2, 60 | "version_minor": 0 61 | }, 62 | "text/plain": [ 63 | "Output()" 64 | ] 65 | }, 66 | "metadata": {}, 67 | "output_type": "display_data" 68 | } 69 | ], 70 | "source": [ 71 | "#HIDDEN\n", 72 | "# please run this cell to get access the GUI app\n", 73 | "from buckinghampy import BuckinghamPiGui\n", 74 | "GUI=BuckinghamPiGui()" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": {}, 80 | "source": [ 81 | "# Example 1: Pressure Drop in Pipe\n", 82 | "---" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [ 89 | "In the following image we represent how to obtain the sets of dimensionless groups for the example of Pressure Drop in a Pipe using the GUI\n", 90 | "\n", 91 | "![Latex Rendered Results](doc/Gui-example.png)" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": 2, 97 | "metadata": {}, 98 | "outputs": [ 99 | { 100 | "data": { 101 | "text/latex": [ 102 | "$$\\text{Set }1: \\quad\\pi_1 = \\frac{R}{d}\\quad\\pi_2 = \\frac{Q \\mu}{d^{3} {\\Delta}p}\\quad$$" 103 | ], 104 | "text/plain": [ 105 | "" 106 | ] 107 | }, 108 | "metadata": {}, 109 | "output_type": "display_data" 110 | }, 111 | { 112 | "data": { 113 | "text/markdown": [ 114 | "---" 115 | ], 116 | "text/plain": [ 117 | "" 118 | ] 119 | }, 120 | "metadata": {}, 121 | "output_type": "display_data" 122 | }, 123 | { 124 | "data": { 125 | "text/latex": [ 126 | "$$\\text{Set }2: \\quad\\pi_1 = \\frac{R \\sqrt[3]{{\\Delta}p}}{\\sqrt[3]{Q} \\sqrt[3]{\\mu}}\\quad\\pi_2 = \\frac{d \\sqrt[3]{{\\Delta}p}}{\\sqrt[3]{Q} \\sqrt[3]{\\mu}}\\quad$$" 127 | ], 128 | "text/plain": [ 129 | "" 130 | ] 131 | }, 132 | "metadata": {}, 133 | "output_type": "display_data" 134 | }, 135 | { 136 | "data": { 137 | "text/markdown": [ 138 | "---" 139 | ], 140 | "text/plain": [ 141 | "" 142 | ] 143 | }, 144 | "metadata": {}, 145 | "output_type": "display_data" 146 | }, 147 | { 148 | "data": { 149 | "text/latex": [ 150 | "$$\\text{Set }3: \\quad\\pi_1 = \\frac{d}{R}\\quad\\pi_2 = \\frac{Q \\mu}{R^{3} {\\Delta}p}\\quad$$" 151 | ], 152 | "text/plain": [ 153 | "" 154 | ] 155 | }, 156 | "metadata": {}, 157 | "output_type": "display_data" 158 | }, 159 | { 160 | "data": { 161 | "text/markdown": [ 162 | "---" 163 | ], 164 | "text/plain": [ 165 | "" 166 | ] 167 | }, 168 | "metadata": {}, 169 | "output_type": "display_data" 170 | } 171 | ], 172 | "source": [ 173 | "from buckinghampy import BuckinghamPi\n", 174 | "\n", 175 | "Pressure_Drop = BuckinghamPi()\n", 176 | "Pressure_Drop.add_variable(name='{\\\\Delta}p',dimensions='M*L^(-1)*T^(-2)') # pressure drop\n", 177 | "Pressure_Drop.add_variable(name='R',dimensions='L') # length of the pipe\n", 178 | "Pressure_Drop.add_variable(name='d',dimensions='L') # diameter of the pipe\n", 179 | "Pressure_Drop.add_variable(name='\\\\mu',dimensions='M*L^(-1)*T^(-1)') # viscosity\n", 180 | "Pressure_Drop.add_variable(name='Q',dimensions='L^(3)*T^(-1)') # volumetic flow rate\n", 181 | "\n", 182 | "Pressure_Drop.generate_pi_terms()\n", 183 | "Pressure_Drop.print_all()" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": null, 189 | "metadata": {}, 190 | "outputs": [], 191 | "source": [] 192 | } 193 | ], 194 | "metadata": { 195 | "kernelspec": { 196 | "display_name": "Python 3", 197 | "language": "python", 198 | "name": "python3" 199 | }, 200 | "language_info": { 201 | "codemirror_mode": { 202 | "name": "ipython", 203 | "version": 3 204 | }, 205 | "file_extension": ".py", 206 | "mimetype": "text/x-python", 207 | "name": "python", 208 | "nbconvert_exporter": "python", 209 | "pygments_lexer": "ipython3", 210 | "version": "3.8.3" 211 | }, 212 | "toc": { 213 | "colors": { 214 | "hover_highlight": "#DAA520", 215 | "navigate_num": "#000000", 216 | "navigate_text": "#333333", 217 | "running_highlight": "#FF0000", 218 | "selected_highlight": "#FFD700", 219 | "sidebar_border": "#EEEEEE", 220 | "wrapper_background": "#FFFFFF" 221 | }, 222 | "moveMenuLeft": true, 223 | "nav_menu": { 224 | "height": "46px", 225 | "width": "252px" 226 | }, 227 | "navigate_menu": true, 228 | "number_sections": false, 229 | "sideBar": true, 230 | "threshold": 4, 231 | "toc_cell": false, 232 | "toc_section_display": "block", 233 | "toc_window_display": false, 234 | "widenNotebook": false 235 | }, 236 | "varInspector": { 237 | "cols": { 238 | "lenName": 16, 239 | "lenType": 16, 240 | "lenVar": 40 241 | }, 242 | "kernels_config": { 243 | "python": { 244 | "delete_cmd_postfix": "", 245 | "delete_cmd_prefix": "del ", 246 | "library": "var_list.py", 247 | "varRefreshCmd": "print(var_dic_list())" 248 | }, 249 | "r": { 250 | "delete_cmd_postfix": ") ", 251 | "delete_cmd_prefix": "rm(", 252 | "library": "var_list.r", 253 | "varRefreshCmd": "cat(var_dic_list()) " 254 | } 255 | }, 256 | "types_to_exclude": [ 257 | "module", 258 | "function", 259 | "builtin_function_or_method", 260 | "instance", 261 | "_Feature" 262 | ], 263 | "window_display": false 264 | } 265 | }, 266 | "nbformat": 4, 267 | "nbformat_minor": 2 268 | } 269 | -------------------------------------------------------------------------------- /buckinghampy/__init__.py: -------------------------------------------------------------------------------- 1 | from .buckinghampi import BuckinghamPi 2 | from .buckinghampigui import BuckinghamPiGui -------------------------------------------------------------------------------- /buckinghampy/buckinghampi.py: -------------------------------------------------------------------------------- 1 | """buckinghampi.py: a symbolic module that generates the pi terms based on some variables by applying the pi-theorem.""" 2 | 3 | __author__ = "Mokbel Karam" 4 | __copyright__ = "Copyright (c) 2021, Mokbel Karam" 5 | 6 | __credits__ = ["University of Utah Department of Chemical Engineering"] 7 | __license__ = "MIT" 8 | __version__ = "1.0.3" 9 | __maintainer__ = "Mokbel Karam" 10 | __email__ = "karammokbel@gmail.com" 11 | __status__ = "Production" 12 | 13 | import sympy as sp 14 | from sympy.parsing.sympy_parser import parse_expr 15 | from sympy.core.mul import Mul, Pow 16 | from sympy.core.expr import Expr 17 | import numpy as np 18 | from itertools import combinations,permutations 19 | from tabulate import tabulate 20 | 21 | try: 22 | from IPython.display import display, clear_output, Math, Markdown 23 | except: 24 | pass 25 | 26 | class BuckinghamPi: 27 | def __init__(self): 28 | ''' 29 | Construct an instance of the BuckinghamPi theorem 30 | ''' 31 | self.__var_from_idx={} 32 | self.__idx_from_var = {} 33 | self.__variables={} 34 | self.__sym_variables={} 35 | self.__flagged_var = {'var_name':None, 'var_index':None,'selected':False} 36 | 37 | self.__null_spaces = [] 38 | 39 | self.__fundamental_vars_used = [] # list of fundamental variables being used 40 | 41 | self.__prefixed_dimensionless_terms = [] 42 | 43 | self.__flagged_var_max_sets = 20 44 | 45 | @property 46 | def fundamental_variables(self): 47 | ''' 48 | :return: a list of the fundamental variables being used 49 | ''' 50 | return self.__fundamental_vars_used 51 | 52 | @property 53 | def variables(self): 54 | ''' 55 | :return: a dict of the variables added by the user. 56 | ''' 57 | return self.__variables 58 | 59 | 60 | def __parse_expression(self,string:str): 61 | if '^' in string: 62 | # convert the xor operator to power operator 63 | string = string.replace('^','**') 64 | 65 | expr = parse_expr(string.lower()) 66 | 67 | if not (isinstance(expr,Mul) or isinstance(expr,Pow) or isinstance(expr,sp.Symbol)): 68 | raise Exception('expression of type {} is not of the accepted types ({}, {}, {})'.format(type(expr), Mul, Pow, sp.Symbol)) 69 | if expr.as_coeff_Mul()[0] != 1: 70 | raise Exception('cannot have coefficients, {}, that multiply the expression {}'.format(expr.as_coeff_Mul()[0],expr.as_coeff_Mul()[1])) 71 | 72 | #extract the physical dimensions from the dimensions expressions 73 | used_symbols = list(expr.free_symbols) 74 | for sym in used_symbols: 75 | if not sym in self.__fundamental_vars_used: 76 | self.__fundamental_vars_used.append(sym) 77 | 78 | return expr 79 | 80 | def __extract_exponents(self,expr:Expr): 81 | num_physical_dimensions = len(self.__fundamental_vars_used) 82 | vect = np.zeros(num_physical_dimensions) 83 | args = list(expr.args) if list(expr.args) else [expr] 84 | # print(args) 85 | if isinstance(expr, Pow): 86 | vect[self.__fundamental_vars_used.index(args[0])] = int(args[1]) 87 | else: 88 | for e in args: 89 | if isinstance(expr, sp.Symbol): 90 | vect[self.__fundamental_vars_used.index(e)]= int(1) 91 | # print('({}, {})'.format(e, 1)) 92 | else: 93 | var, exponent= e.as_base_exp() 94 | vect[self.__fundamental_vars_used.index(var)] = int(exponent) 95 | # print('({}, {})'.format(var, exponent)) 96 | 97 | return vect 98 | 99 | def add_variable(self, name: str, dimensions: str, non_repeating=False): 100 | ''' 101 | Add variables to use for the pi-theorem 102 | :param name: (string) name of the variable to be added 103 | :param dimensions: (string) expression of the independent physical variable expressed in terms of the k independent fundamental dimensions. 104 | :param non_repeating: (boolean) select a variable to belong to the non-repeating variables matrix. This will ensure that the selected variable 105 | only shows up in one dimensionless group. 106 | ''' 107 | if dimensions!="1": 108 | expr = self.__parse_expression(dimensions) 109 | self.__variables.update({name:expr}) 110 | var_idx = len(list(self.__variables.keys()))-1 111 | self.__var_from_idx[var_idx]= name 112 | self.__idx_from_var[name] = var_idx 113 | if non_repeating and (self.__flagged_var['selected'] == False): 114 | self.__flagged_var['var_name'] = name 115 | self.__flagged_var['var_index'] = var_idx 116 | self.__flagged_var['selected'] = True 117 | elif non_repeating and (self.__flagged_var['selected'] == True): 118 | raise Exception("you cannot select more than one variable at a time to be a non_repeating.") 119 | else: 120 | self.__prefixed_dimensionless_terms.append(sp.symbols(name)) 121 | 122 | def __create_M(self): 123 | self.num_variable = len(list(self.__variables.keys())) 124 | num_physical_dimensions = len(self.__fundamental_vars_used) 125 | if self.num_variable <= num_physical_dimensions: 126 | raise Exception('The number of variables has to be greater than the number of physical dimensions.') 127 | 128 | self.M = np.zeros(shape=(self.num_variable, num_physical_dimensions)) 129 | # fill M 130 | for var_name in self.__variables.keys(): 131 | expr = self.__variables[var_name] 132 | vect = self.__extract_exponents(expr) 133 | row = self.__idx_from_var[var_name] 134 | self.M[row, :] = vect 135 | 136 | self.M = self.M.transpose() 137 | 138 | def __create_symbolic_variables(self): 139 | for var_name in self.__variables.keys(): 140 | self.__sym_variables[var_name] = sp.symbols(var_name) 141 | 142 | def __solve_null_spaces(self): 143 | if self.__flagged_var['selected']==True: 144 | self.__solve_null_spaces_for_flagged_variables() 145 | 146 | else: 147 | for idx in self.__var_from_idx.keys(): 148 | self.__flagged_var['var_name'] = self.__var_from_idx[idx] 149 | self.__flagged_var['var_index'] = idx 150 | self.__flagged_var['selected'] = True 151 | 152 | self.__solve_null_spaces_for_flagged_variables() 153 | 154 | def __solve_null_spaces_for_flagged_variables(self): 155 | 156 | assert self.__flagged_var['selected']==True, " you need to select a variable to be explicit" 157 | 158 | n = self.num_variable 159 | m = len(self.__fundamental_vars_used) 160 | 161 | original_indicies = list(range(0, n)) 162 | all_idx = original_indicies.copy() 163 | if self.__flagged_var['selected']: 164 | del all_idx[self.__flagged_var['var_index']] 165 | 166 | # print(all_idx) 167 | all_combs = list(combinations(all_idx,m))[:self.__flagged_var_max_sets] 168 | # print(all_combs) 169 | 170 | num_det_0 = 0 171 | for comb in all_combs: 172 | temp_comb = list(comb).copy() 173 | extra_vars = [i for i in original_indicies if i not in temp_comb ] 174 | b_ns = [] 175 | for extra_var in extra_vars: 176 | new_order = {} 177 | temp_comb.append(extra_var) 178 | A = self.M[:,temp_comb].copy() 179 | for num,var_idx in enumerate(temp_comb): 180 | new_order[num] = self.__var_from_idx[var_idx] 181 | B = sp.Matrix(A) 182 | test_mat = B[:,:m] 183 | if sp.det(test_mat) !=0: 184 | ns = B.nullspace()[0] 185 | b_ns.append({'order': new_order, 'power': ns.tolist()}) 186 | 187 | else: 188 | num_det_0+=1 189 | temp_comb = list(comb).copy() 190 | if b_ns: # if b_ns is not empty add it to the nullspaces list 191 | self.__null_spaces.append(b_ns) 192 | # print("num of det 0 : ",num_det_0) 193 | 194 | def __construct_symbolic_pi_terms(self): 195 | self.__allpiterms = [] 196 | for space in self.__null_spaces: 197 | spacepiterms = [] 198 | for term in space: 199 | expr = 1 200 | idx = 0 201 | for order,power in zip(term['order'].keys(),term['power']): 202 | expr *= self.__sym_variables[term['order'][order]] ** sp.nsimplify(sp.Rational(power[0])) 203 | idx += 1 204 | spacepiterms.append(expr) 205 | # check for already existing pi terms in previous null-spaces 206 | already_exists = False 207 | for previouspiterms in self.__allpiterms: 208 | if all(x in previouspiterms for x in spacepiterms): 209 | already_exists = True 210 | break 211 | if not already_exists: 212 | self.__allpiterms.append(spacepiterms) 213 | 214 | def __rm_duplicated_powers(self): 215 | # this algorithm rely on the fact that the nullspace function 216 | # in sympy set one free variable to 1 and the all other to zero 217 | # then solve the system by back substitution. 218 | duplicate = [] 219 | dummy_other_terms = self.__allpiterms.copy() 220 | for num_set, pi_set in enumerate(self.__allpiterms): 221 | dummy_other_terms.remove(pi_set) 222 | for num_other, other in enumerate(dummy_other_terms): 223 | permutations_sets = permutations(pi_set) 224 | for p_set in permutations_sets: 225 | # create a permutation vector from the permutation set 226 | p_V = sp.Matrix(list(p_set)) 227 | # create a vector from the other set of dimensionless groups that we are comparing to. 228 | o_V = sp.Matrix(other) 229 | # create an element wise inverse of the vector of dimensionless groups 230 | o_V_inv = o_V.applyfunc(lambda x:x**(-1)) 231 | 232 | result = sp.matrix_multiply_elementwise(p_V, o_V) 233 | # obtain the index of numerical value in the result vector. 234 | # numerical values indicates that one dimensionless group is the inverse of the other group 235 | # in this algorithm the numerical value will be equal to 1 (this is a result of the nullspace function in sympy) 236 | idx_num_result = [x for x in range(len(p_set)) if isinstance(result[x,0],sp.Number)] 237 | # also repeat the multiplication with the inverse vector 238 | result_inv = sp.matrix_multiply_elementwise(p_V, o_V_inv) 239 | # check for the index of the numerical values in the result vector 240 | idx_num_result_inv = [x for x in range(len(p_set)) if isinstance(result_inv[x,0],sp.Number)] 241 | # concatinate the indices into one list 242 | all_indices = idx_num_result + idx_num_result_inv 243 | # compare if the two vector are duplicates 244 | if set(all_indices) == set(list(range(len(p_set)))): 245 | duplicate.append(pi_set) 246 | 247 | # remove duplicates from the main dict of all pi terms 248 | for dup in duplicate: 249 | if dup in self.__allpiterms: 250 | self.__allpiterms.remove(dup) 251 | return duplicate 252 | 253 | def __populate_prefixed_dimensionless_groups(self): 254 | for num_set, pi_set in enumerate(self.__allpiterms): 255 | for pre_fixed_dimensionless_group in self.__prefixed_dimensionless_terms: 256 | self.__allpiterms[num_set].append(pre_fixed_dimensionless_group) 257 | 258 | def generate_pi_terms(self): 259 | ''' 260 | Generates all the possible pi terms 261 | ''' 262 | self.__create_M() 263 | 264 | self.__create_symbolic_variables() 265 | 266 | self.__solve_null_spaces() 267 | 268 | self.__construct_symbolic_pi_terms() 269 | 270 | self.__rm_duplicated_powers() 271 | 272 | self.__populate_prefixed_dimensionless_groups() 273 | 274 | @property 275 | def pi_terms(self): 276 | ''' 277 | :return: a list with all the symbolic dimensionless terms for all permutation of the dimensional Matrix M 278 | ''' 279 | return self.__allpiterms 280 | 281 | 282 | def __Jupyter_print(self): 283 | ''' print the rendered Latex format in Jupyter cell''' 284 | for set_num, space in enumerate(self.__allpiterms): 285 | latex_str= '\\text{Set }' 286 | latex_str+='{}: \\quad'.format(set_num+1) 287 | for num, term in enumerate(space): 288 | latex_str += '\\pi_{} = '.format(num+1)+sp.latex(term) 289 | latex_str += '\\quad' 290 | display(Math(latex_str)) 291 | display(Markdown('---')) 292 | 293 | def __get_latex_form(self,latex_string=False): 294 | latex_form = [] 295 | for pi_set in self.__allpiterms: 296 | latex_set = [] 297 | for pi in pi_set: 298 | if latex_string: 299 | if latex_string: 300 | latex_set.append(sp.latex(pi)) 301 | else: 302 | latex_set.append(pi) 303 | else: 304 | latex_set.append(pi) 305 | latex_form.append(latex_set) 306 | 307 | for num, set in enumerate(latex_form): 308 | set.insert(0, num + 1) 309 | 310 | return latex_form 311 | 312 | def __tabulate_print(self,latex_string=False): 313 | ''' print the dimensionless sets in a tabulated format''' 314 | latex_sets = self.__get_latex_form(latex_string) 315 | n = self.num_variable 316 | m = len(self.__fundamental_vars_used) 317 | 318 | num_of_pi_terms = n - m 319 | 320 | headers = ['sets'] 321 | for num in range(num_of_pi_terms): 322 | headers.append('Pi {}'.format(num + 1)) 323 | print(tabulate(latex_sets, headers=headers)) 324 | 325 | def print_all(self, latex_string=False): 326 | ''' 327 | print all the sets of dimensionless groups in latex or symbolic form. 328 | :latex_string: optional boolean. If set to True the function will print the latex string of the 329 | dimensionless groups. if set to False the function will print the symbolic form of the 330 | dimensionless groups. 331 | ''' 332 | try: 333 | ''' Try to render the latex in Jupyter cell''' 334 | self.__Jupyter_print() 335 | except: 336 | ''' print the dimensionless sets in a tabulated format when in terminal session''' 337 | self.__tabulate_print(latex_string) 338 | 339 | def return_all(self,latex_string=False): 340 | ''' return all of the latex output in a dict for easier downstream processing ''' 341 | return self.__get_latex_form(latex_string) -------------------------------------------------------------------------------- /buckinghampy/buckinghampigui.py: -------------------------------------------------------------------------------- 1 | from buckinghampy.buckinghampi import BuckinghamPi,sp 2 | try: 3 | import ipywidgets as widgets 4 | from ipywidgets import HBox, VBox, Layout, Box 5 | from IPython.display import display, clear_output, Math, Markdown 6 | except: 7 | pass 8 | 9 | 10 | 11 | class BuckinghamPiGui(object): 12 | 13 | def __init__(self): 14 | import sys 15 | inJupyter = sys.argv[-1].endswith('json') 16 | if inJupyter == False: 17 | raise Exception("Cannot instantiate the class in a non jupyter cell!") 18 | 19 | self.continuousUpdate=True 20 | self.style = {'description_width': 'initial'} 21 | self.txt_box_layout = Layout(width='auto', height='32px') 22 | self.children_vbox = [] # variable list 23 | self.panels={} 24 | self.stylingTab() 25 | 26 | self.__display(self.panels) 27 | 28 | self.on_change() 29 | self.generateButtonPressed = False 30 | 31 | def __display(self,obj): 32 | for key in obj.keys(): 33 | display(obj[key]) 34 | 35 | def tabs_disiplay(self): 36 | self.tab = widgets.Tab(children=self.tabs) 37 | self.tab.set_title(0, 'results') 38 | self.tab.set_title(1, 'Numerical Setup') 39 | self.tab.set_title(2, 'style') 40 | 41 | def stylingTab(self): 42 | 43 | self.num_var = widgets.BoundedIntText( 44 | value=0, 45 | description='Number of Variables:', 46 | continuous_update=self.continuousUpdate, 47 | style=self.style 48 | ) 49 | 50 | self.top_panel=HBox(children=[self.num_var]) 51 | self.panels['top_panel']= self.top_panel 52 | 53 | def create_variable_Hbox(self,idx): 54 | 55 | setattr(self, 'var_name_{}'.format(idx), 56 | widgets.Textarea( 57 | placeholder='var name', 58 | description='Name:', 59 | layout=self.txt_box_layout), 60 | ) 61 | 62 | setattr(self, 'var_dimensions_{}'.format(idx), 63 | widgets.Textarea( 64 | placeholder='var dimensions', 65 | description='Dimensions:', 66 | layout=self.txt_box_layout) 67 | ) 68 | 69 | setattr(self, 'var_select_{}'.format(idx), 70 | widgets.Checkbox( 71 | value=False, 72 | description='Non-repeating') 73 | ) 74 | 75 | box_layout = Layout(display='flex', 76 | flex_flow='row', 77 | align_items='flex-start', 78 | # border='solid', 79 | width='auto') 80 | items = [getattr(self, 'var_name_{}'.format(idx)), getattr(self, 'var_dimensions_{}'.format(idx)), getattr(self, 'var_select_{}'.format(idx))] 81 | box = Box(children=items, layout=box_layout) 82 | 83 | 84 | setattr(self, 'var_{}'.format(idx),box) 85 | 86 | 87 | def create_bottom_panel(self,change): 88 | if len(self.children_vbox): 89 | del self.children_vbox[-1] 90 | list_var_num = len(self.children_vbox) 91 | print("children v box list",self.children_vbox) 92 | print("list variable number ",list_var_num) 93 | print("variable number ",self.num_var.value) 94 | counter = list_var_num 95 | while counter < self.num_var.value: 96 | counter += 1 97 | self.create_variable_Hbox(counter) 98 | self.children_vbox.append(getattr(self, 'var_{}'.format(counter))) 99 | 100 | 101 | counter = list_var_num 102 | while counter > self.num_var.value: 103 | del self.children_vbox[-1] 104 | delattr(self,'var_{}'.format(counter)) 105 | delattr(self, 'var_name_{}'.format(counter)) 106 | delattr(self, 'var_dimensions_{}'.format(counter)) 107 | delattr(self,'var_select_{}'.format(counter)) 108 | counter -= 1 109 | 110 | self.generate_button_widget = widgets.Button( 111 | description='Generate', 112 | disabled=False, 113 | button_style='', # 'success', 'info', 'warning', 'danger' or '' 114 | tooltip='Generate', 115 | icon='check' 116 | ) 117 | 118 | self.children_vbox.append(self.generate_button_widget) 119 | 120 | self.bottom_panel = \ 121 | VBox(children=self.children_vbox) 122 | 123 | self.uncheck_chk_boxes() 124 | 125 | self.panels['bottom_panel'] = self.bottom_panel 126 | clear_output() 127 | self.__display(self.panels) 128 | self.output = widgets.Output() 129 | display(self.output) 130 | 131 | for idx in range(1,self.num_var.value+1): 132 | select_obj = getattr(self, 'var_select_{}'.format(idx)) 133 | select_obj.observe(self.var_checkboxes_enable, names='value') 134 | 135 | self.generate_button_widget.on_click(self.generate_pressed) 136 | 137 | def uncheck_chk_boxes(self): 138 | all_chk_boxes = [getattr(self, 'var_select_{}'.format(idx)) for idx in range(1, self.num_var.value + 1)] 139 | chk_boxes_vals = [chk_box.value for chk_box in all_chk_boxes] 140 | for idx, chk_box in enumerate(all_chk_boxes): 141 | chk_box.value = False 142 | 143 | def change_visibility_select_box(self): 144 | all_chk_boxes = [getattr(self, 'var_select_{}'.format(idx)) for idx in range(1,self.num_var.value+1)] 145 | chk_boxes_vals = [chk_box.value for chk_box in all_chk_boxes] 146 | try: 147 | idx_true = chk_boxes_vals.index(True) + 1 148 | if idx_true: 149 | for idx, chk_box in enumerate(all_chk_boxes): 150 | if idx + 1 != idx_true: 151 | chk_box.disabled = not(chk_box.disabled) 152 | except: 153 | for idx, chk_box in enumerate(all_chk_boxes): 154 | chk_box.disabled = False 155 | 156 | def var_checkboxes_enable(self,change): 157 | self.change_visibility_select_box() 158 | 159 | def on_change(self): 160 | self.num_var.observe(self.create_bottom_panel,names='value') 161 | 162 | 163 | 164 | def collect_data(self): 165 | self.data={} 166 | var_num = self.num_var.value 167 | self.data['var_num'] = var_num 168 | self.data['vars'] ={} 169 | for idx in range(1,var_num+1): 170 | var_name = getattr(self, 'var_name_{}'.format(idx)).value 171 | var_dimensions = getattr(self, 'var_dimensions_{}'.format(idx)).value 172 | var_select = getattr(self, 'var_select_{}'.format(idx)).value 173 | 174 | self.data['vars'][var_name] = {'dimensions':var_dimensions,'non_repeating':var_select} 175 | 176 | def generate_solution(self): 177 | problem = BuckinghamPi() 178 | for varname in self.data['vars'].keys(): 179 | problem.add_variable(name=varname, dimensions=self.data['vars'][varname]['dimensions'], 180 | non_repeating=self.data['vars'][varname]['non_repeating']) 181 | problem.generate_pi_terms() 182 | self.data['sol'] = problem.pi_terms 183 | 184 | def generate_pressed(self, *args): 185 | self.collect_data() 186 | 187 | try: 188 | self.generate_solution() 189 | with self.output: 190 | clear_output() 191 | self.print() 192 | except Exception as e: 193 | with self.output: 194 | clear_output() 195 | print(e) 196 | 197 | def print(self): 198 | for set_num, space in enumerate(self.data['sol']): 199 | latex_str= '\\text{Set }' 200 | latex_str+='{}: \\quad'.format(set_num+1) 201 | for num, term in enumerate(space): 202 | latex_str += '\\pi_{} = '.format(num+1)+sp.latex(term) 203 | latex_str += '\\quad' 204 | display(Math(latex_str)) 205 | display(Markdown('---')) -------------------------------------------------------------------------------- /doc/Gui-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saadgroup/BuckinghamPy/1fa26797d8f74faff3af59011c11a661be1ff1e4/doc/Gui-example.png -------------------------------------------------------------------------------- /doc/buckinghampi.m.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | buckinghampi API documentation 7 | 8 | 9 | 10 | 11 | 551 | 552 | 853 | 854 | 925 | 926 | 1018 | 1019 | 1033 | 1034 | 1035 | Top 1036 | 1037 |
1038 | 1039 | 1040 | 1064 | 1065 |
1066 | 1067 | 1068 | 1069 | 1070 | 1071 | 1072 |
1073 |

buckinghampi module

1074 |

buckinghampi.py: a symbolic module that generates the pi terms based on some variables by applying the pi-theorem.

1075 | 1076 | 1077 |
1078 |
"""buckinghampi.py: a symbolic module that generates the pi terms based on some variables by applying the pi-theorem."""
1079 | 
1080 | __author__ = "Mokbel Karam"
1081 | __copyright__ = "Copyright (c) 2021, Mokbel Karam"
1082 | 
1083 | __credits__ = ["University of Utah Department of Chemical Engineering"]
1084 | __license__ = "MIT"
1085 | __version__ = "1.0.3"
1086 | __maintainer__ = "Mokbel Karam"
1087 | __email__ = "karammokbel@gmail.com"
1088 | __status__ = "Production"
1089 | 
1090 | import sympy as sp
1091 | from sympy.parsing.sympy_parser import parse_expr
1092 | from sympy.core.mul import Mul, Pow
1093 | from sympy.core.expr import Expr
1094 | import numpy as np
1095 | from itertools import combinations,permutations
1096 | from tabulate import tabulate
1097 | 
1098 | try:
1099 |     from IPython.display import display, clear_output, Math, Markdown
1100 | except:
1101 |     pass
1102 | 
1103 | class BuckinghamPi:
1104 |     def __init__(self):
1105 |         '''
1106 |         Construct an instance of the BuckinghamPi theorem
1107 |         '''
1108 |         self.__var_from_idx={}
1109 |         self.__idx_from_var = {}
1110 |         self.__variables={}
1111 |         self.__sym_variables={}
1112 |         self.__flagged_var = {'var_name':None, 'var_index':None,'selected':False}
1113 | 
1114 |         self.__null_spaces = []
1115 | 
1116 |         self.__fundamental_vars_used = [] # list of fundamental variables being used
1117 | 
1118 |     @property
1119 |     def fundamental_variables(self):
1120 |         '''
1121 |         :return: a list of the fundamental variables being used
1122 |         '''
1123 |         return self.__fundamental_vars_used
1124 | 
1125 |     @property
1126 |     def variables(self):
1127 |         '''
1128 |         :return: a dict of the variables added by the user.
1129 |         '''
1130 |         return self.__variables
1131 | 
1132 | 
1133 |     def __parse_expression(self,string:str):
1134 |         if '^' in string:
1135 |             # convert the xor operator to power operator
1136 |             string = string.replace('^','**')
1137 | 
1138 |         expr = parse_expr(string.lower())
1139 | 
1140 |         if not (isinstance(expr,Mul) or isinstance(expr,Pow) or isinstance(expr,sp.Symbol)):
1141 |             raise Exception('expression of type {} is not of the accepted types ({}, {}, {})'.format(type(expr), Mul, Pow, sp.Symbol))
1142 |         if expr.as_coeff_Mul()[0] != 1:
1143 |             raise Exception('cannot have coefficients, {}, that multiply the expression {}'.format(expr.as_coeff_Mul()[0],expr.as_coeff_Mul()[1]))
1144 | 
1145 |         #extract the physical dimensions from the units expressions
1146 |         used_symbols = list(expr.free_symbols)
1147 |         for sym in used_symbols:
1148 |             if not sym in self.__fundamental_vars_used:
1149 |                 self.__fundamental_vars_used.append(sym)
1150 | 
1151 |         return expr
1152 | 
1153 |     def __extract_exponents(self,expr:Expr):
1154 |         num_physical_dimensions = len(self.__fundamental_vars_used)
1155 |         vect = np.zeros(num_physical_dimensions)
1156 |         args = list(expr.args) if list(expr.args) else [expr]
1157 |         # print(args)
1158 |         if isinstance(expr, Pow):
1159 |             vect[self.__fundamental_vars_used.index(args[0])] = int(args[1])
1160 |         else:
1161 |             for e in args:
1162 |                 if isinstance(expr, sp.Symbol):
1163 |                     vect[self.__fundamental_vars_used.index(e)]= int(1)
1164 |                     # print('({}, {})'.format(e, 1))
1165 |                 else:
1166 |                     var, exponent= e.as_base_exp()
1167 |                     vect[self.__fundamental_vars_used.index(var)] = int(exponent)
1168 |                     # print('({}, {})'.format(var, exponent))
1169 | 
1170 |         return vect
1171 | 
1172 |     def add_variable(self, name: str, units: str, non_repeating=False):
1173 |         '''
1174 |         Add variables to use for the pi-theorem
1175 |         :param name: (string) name of the variable to be added
1176 |         :param units: (string) expression of the independent physical variable expressed in terms of the k independent fundamental units.
1177 |         :param non_repeating: (boolean) select a variable to belong to the non-repeating variables matrix. This will ensure that the selected variable
1178 |                                         only shows up in one dimensionless group.
1179 |         '''
1180 |         expr =  self.__parse_expression(units)
1181 |         self.__variables.update({name:expr})
1182 |         var_idx = len(list(self.__variables.keys()))-1
1183 |         self.__var_from_idx[var_idx]= name
1184 |         self.__idx_from_var[name] = var_idx
1185 |         if non_repeating and (self.__flagged_var['selected'] == False):
1186 |             self.__flagged_var['var_name'] = name
1187 |             self.__flagged_var['var_index'] = var_idx
1188 |             self.__flagged_var['selected'] = True
1189 |         elif non_repeating and (self.__flagged_var['selected'] == True):
1190 |             raise Exception("you cannot select more than one variable at a time to be a non_repeating.")
1191 | 
1192 |     def __create_M(self):
1193 |         self.num_variable = len(list(self.__variables.keys()))
1194 |         num_physical_dimensions = len(self.__fundamental_vars_used)
1195 |         if self.num_variable <= num_physical_dimensions:
1196 |             raise Exception('The number of variables has to be greater than the number of physical dimensions.')
1197 | 
1198 |         self.M = np.zeros(shape=(self.num_variable, num_physical_dimensions))
1199 |         # fill M
1200 |         for var_name in self.__variables.keys():
1201 |             expr = self.__variables[var_name]
1202 |             vect = self.__extract_exponents(expr)
1203 |             row = self.__idx_from_var[var_name]
1204 |             self.M[row, :] = vect
1205 | 
1206 |         self.M = self.M.transpose()
1207 | 
1208 |     def __create_symbolic_variables(self):
1209 |         for var_name in self.__variables.keys():
1210 |             self.__sym_variables[var_name] = sp.symbols(var_name)
1211 | 
1212 |     def __solve_null_spaces(self):
1213 |         if self.__flagged_var['selected']==True:
1214 |             self.__solve_null_spaces_for_flagged_variables()
1215 | 
1216 |         else:
1217 |             for idx in self.__var_from_idx.keys():
1218 |                 self.__flagged_var['var_name'] = self.__var_from_idx[idx]
1219 |                 self.__flagged_var['var_index'] = idx
1220 |                 self.__flagged_var['selected'] = True
1221 | 
1222 |                 self.__solve_null_spaces_for_flagged_variables()
1223 | 
1224 |     def __solve_null_spaces_for_flagged_variables(self):
1225 | 
1226 |         assert self.__flagged_var['selected']==True, " you need to select a variable to be explicit"
1227 | 
1228 |         n = self.num_variable
1229 |         m = len(self.__fundamental_vars_used)
1230 | 
1231 |         original_indicies = list(range(0, n))
1232 |         all_idx = original_indicies.copy()
1233 |         if self.__flagged_var['selected']:
1234 |             del all_idx[self.__flagged_var['var_index']]
1235 | 
1236 |         # print(all_idx)
1237 |         all_combs = list(combinations(all_idx,m))
1238 |         # print(all_combs)
1239 | 
1240 |         num_det_0 = 0
1241 |         for comb in all_combs:
1242 |             temp_comb = list(comb).copy()
1243 |             extra_vars = [i for i in original_indicies if i not in temp_comb ]
1244 |             b_ns = []
1245 |             for extra_var in extra_vars:
1246 |                 new_order = {}
1247 |                 temp_comb.append(extra_var)
1248 |                 A = self.M[:,temp_comb].copy()
1249 |                 for num,var_idx in enumerate(temp_comb):
1250 |                     new_order[num] =  self.__var_from_idx[var_idx]
1251 |                 B = sp.Matrix(A)
1252 |                 test_mat = B[:,:m]
1253 |                 if sp.det(test_mat) !=0:
1254 |                     ns = B.nullspace()[0]
1255 |                     b_ns.append({'order': new_order, 'power': ns.tolist()})
1256 | 
1257 |                 else:
1258 |                     num_det_0+=1
1259 |                 temp_comb = list(comb).copy()
1260 |             if b_ns: # if b_ns is not empty add it to the nullspaces list
1261 |                 self.__null_spaces.append(b_ns)
1262 |         # print("num of det 0 : ",num_det_0)
1263 | 
1264 |     def __construct_symbolic_pi_terms(self):
1265 |         self.__allpiterms = []
1266 |         for space in self.__null_spaces:
1267 |             spacepiterms = []
1268 |             for term in space:
1269 |                 expr = 1
1270 |                 idx = 0
1271 |                 for order,power in zip(term['order'].keys(),term['power']):
1272 |                     expr *= self.__sym_variables[term['order'][order]] ** sp.nsimplify(sp.Rational(power[0]))
1273 |                     idx += 1
1274 |                 spacepiterms.append(expr)
1275 |             # check for already existing pi terms in previous null-spaces
1276 |             already_exists = False
1277 |             for previouspiterms in self.__allpiterms:
1278 |                 if all(x in previouspiterms for x in spacepiterms):
1279 |                     already_exists = True
1280 |                     break
1281 |             if not already_exists:
1282 |                 self.__allpiterms.append(spacepiterms)
1283 | 
1284 |     def __rm_duplicated_powers(self):
1285 |         # this algorithm rely on the fact that the nullspace function
1286 |         # in sympy set one free variable to 1 and the all other to zero
1287 |         # then solve the system by back substitution.
1288 |         duplicate = []
1289 |         dummy_other_terms = self.__allpiterms.copy()
1290 |         for num_set, pi_set in enumerate(self.__allpiterms):
1291 |             dummy_other_terms.remove(pi_set)
1292 |             for num_other, other in enumerate(dummy_other_terms):
1293 |                 permutations_sets = permutations(pi_set)
1294 |                 for p_set in permutations_sets:
1295 |                     # create a permutation vector from the permutation set
1296 |                     p_V = sp.Matrix(list(p_set))
1297 |                     # create a vector from the other set of dimensionless groups that we are comparing to.
1298 |                     o_V = sp.Matrix(other)
1299 |                     # create an element wise inverse of the vector of dimensionless groups
1300 |                     o_V_inv = o_V.applyfunc(lambda x:x**(-1))
1301 | 
1302 |                     result = sp.matrix_multiply_elementwise(p_V, o_V)
1303 |                     # obtain the index of numerical value in the result vector.
1304 |                     # numerical values indicates that one dimensionless group is the inverse of the other group
1305 |                     # in this algorithm the numerical value will be equal to 1 (this is a result of the nullspace function in sympy)
1306 |                     idx_num_result = [x for x in range(len(p_set)) if isinstance(result[x,0],sp.Number)]
1307 |                     # also repeat the multiplication with the inverse vector
1308 |                     result_inv = sp.matrix_multiply_elementwise(p_V, o_V_inv)
1309 |                     # check for the index of the numerical values in the result vector
1310 |                     idx_num_result_inv = [x for x in range(len(p_set)) if isinstance(result_inv[x,0],sp.Number)]
1311 |                     # concatinate the indices into one list
1312 |                     all_indices = idx_num_result + idx_num_result_inv
1313 |                     # compare if the two vector are duplicates
1314 |                     if set(all_indices) == set(list(range(len(p_set)))):
1315 |                         duplicate.append(pi_set)
1316 | 
1317 |         # remove duplicates from the main dict of all pi terms
1318 |         for dup in duplicate:
1319 |             self.__allpiterms.remove(dup)
1320 |         return duplicate
1321 | 
1322 |     def generate_pi_terms(self):
1323 |         '''
1324 |         Generates all the possible pi terms
1325 |         '''
1326 |         self.__create_M()
1327 | 
1328 |         self.__create_symbolic_variables()
1329 | 
1330 |         self.__solve_null_spaces()
1331 | 
1332 |         self.__construct_symbolic_pi_terms()
1333 | 
1334 |         self.__rm_duplicated_powers()
1335 | 
1336 |     @property
1337 |     def pi_terms(self):
1338 |         '''
1339 |         :return: a list with all the symbolic dimensionless terms for all permutation of the dimensional Matrix M
1340 |         '''
1341 |         return self.__allpiterms
1342 | 
1343 | 
1344 |     def __Jupyter_print(self):
1345 |         ''' print the rendered Latex format in Jupyter cell'''
1346 |         for set_num, space in enumerate(self.__allpiterms):
1347 |             latex_str= '\\text{Set }'
1348 |             latex_str+='{}: \\quad'.format(set_num+1)
1349 |             for num, term in enumerate(space):
1350 |                 latex_str += '\\pi_{} = '.format(num+1)+sp.latex(term)
1351 |                 latex_str += '\\quad'
1352 |             display(Math(latex_str))
1353 |             display(Markdown('---'))
1354 | 
1355 |     def __tabulate_print(self,latex_string=False):
1356 |         ''' print the dimensionless sets in a tabulated format'''
1357 | 
1358 |         latex_form = []
1359 |         for pi_set in self.__allpiterms:
1360 |             latex_set = []
1361 |             for pi in pi_set:
1362 |                 if latex_string:
1363 |                     if latex_string:
1364 |                         latex_set.append(sp.latex(pi))
1365 |                     else:
1366 |                         latex_set.append(pi)
1367 |                 else:
1368 |                     latex_set.append(pi)
1369 |             latex_form.append(latex_set)
1370 | 
1371 |         num_of_pi_terms = len(latex_form[0])
1372 | 
1373 |         headers = ['sets']
1374 |         for num in range(num_of_pi_terms):
1375 |             headers.append('Pi {}'.format(num + 1))
1376 | 
1377 |         for num, set in enumerate(latex_form):
1378 |             set.insert(0, num + 1)
1379 | 
1380 |         print(tabulate(latex_form, headers=headers))
1381 | 
1382 |     def print_all(self, latex_string=False):
1383 |         '''
1384 |         print all the sets of dimensionless groups in latex or symbolic form.
1385 |         :latex_string: optional boolean. If set to True the function will print the latex string of the
1386 |                         dimensionless groups. if set to False the function will print the symbolic form of the
1387 |                         dimensionless groups.
1388 |         '''
1389 |         try:
1390 |             ''' Try to render the latex in Jupyter cell'''
1391 |             self.__Jupyter_print()
1392 |         except:
1393 |             ''' print the dimensionless sets in a tabulated format when in terminal session'''
1394 |             self.__tabulate_print(latex_string)
1395 | 
1396 | 1397 |
1398 | 1399 |
1400 | 1401 |
1402 | 1403 | 1404 |

Classes

1405 | 1406 |
1407 |

class BuckinghamPi

1408 | 1409 | 1410 |
1411 | 1412 |
1413 |
class BuckinghamPi:
1414 |     def __init__(self):
1415 |         '''
1416 |         Construct an instance of the BuckinghamPi theorem
1417 |         '''
1418 |         self.__var_from_idx={}
1419 |         self.__idx_from_var = {}
1420 |         self.__variables={}
1421 |         self.__sym_variables={}
1422 |         self.__flagged_var = {'var_name':None, 'var_index':None,'selected':False}
1423 | 
1424 |         self.__null_spaces = []
1425 | 
1426 |         self.__fundamental_vars_used = [] # list of fundamental variables being used
1427 | 
1428 |     @property
1429 |     def fundamental_variables(self):
1430 |         '''
1431 |         :return: a list of the fundamental variables being used
1432 |         '''
1433 |         return self.__fundamental_vars_used
1434 | 
1435 |     @property
1436 |     def variables(self):
1437 |         '''
1438 |         :return: a dict of the variables added by the user.
1439 |         '''
1440 |         return self.__variables
1441 | 
1442 | 
1443 |     def __parse_expression(self,string:str):
1444 |         if '^' in string:
1445 |             # convert the xor operator to power operator
1446 |             string = string.replace('^','**')
1447 | 
1448 |         expr = parse_expr(string.lower())
1449 | 
1450 |         if not (isinstance(expr,Mul) or isinstance(expr,Pow) or isinstance(expr,sp.Symbol)):
1451 |             raise Exception('expression of type {} is not of the accepted types ({}, {}, {})'.format(type(expr), Mul, Pow, sp.Symbol))
1452 |         if expr.as_coeff_Mul()[0] != 1:
1453 |             raise Exception('cannot have coefficients, {}, that multiply the expression {}'.format(expr.as_coeff_Mul()[0],expr.as_coeff_Mul()[1]))
1454 | 
1455 |         #extract the physical dimensions from the units expressions
1456 |         used_symbols = list(expr.free_symbols)
1457 |         for sym in used_symbols:
1458 |             if not sym in self.__fundamental_vars_used:
1459 |                 self.__fundamental_vars_used.append(sym)
1460 | 
1461 |         return expr
1462 | 
1463 |     def __extract_exponents(self,expr:Expr):
1464 |         num_physical_dimensions = len(self.__fundamental_vars_used)
1465 |         vect = np.zeros(num_physical_dimensions)
1466 |         args = list(expr.args) if list(expr.args) else [expr]
1467 |         # print(args)
1468 |         if isinstance(expr, Pow):
1469 |             vect[self.__fundamental_vars_used.index(args[0])] = int(args[1])
1470 |         else:
1471 |             for e in args:
1472 |                 if isinstance(expr, sp.Symbol):
1473 |                     vect[self.__fundamental_vars_used.index(e)]= int(1)
1474 |                     # print('({}, {})'.format(e, 1))
1475 |                 else:
1476 |                     var, exponent= e.as_base_exp()
1477 |                     vect[self.__fundamental_vars_used.index(var)] = int(exponent)
1478 |                     # print('({}, {})'.format(var, exponent))
1479 | 
1480 |         return vect
1481 | 
1482 |     def add_variable(self, name: str, units: str, non_repeating=False):
1483 |         '''
1484 |         Add variables to use for the pi-theorem
1485 |         :param name: (string) name of the variable to be added
1486 |         :param units: (string) expression of the independent physical variable expressed in terms of the k independent fundamental units.
1487 |         :param non_repeating: (boolean) select a variable to belong to the non-repeating variables matrix. This will ensure that the selected variable
1488 |                                         only shows up in one dimensionless group.
1489 |         '''
1490 |         expr =  self.__parse_expression(units)
1491 |         self.__variables.update({name:expr})
1492 |         var_idx = len(list(self.__variables.keys()))-1
1493 |         self.__var_from_idx[var_idx]= name
1494 |         self.__idx_from_var[name] = var_idx
1495 |         if non_repeating and (self.__flagged_var['selected'] == False):
1496 |             self.__flagged_var['var_name'] = name
1497 |             self.__flagged_var['var_index'] = var_idx
1498 |             self.__flagged_var['selected'] = True
1499 |         elif non_repeating and (self.__flagged_var['selected'] == True):
1500 |             raise Exception("you cannot select more than one variable at a time to be a non_repeating.")
1501 | 
1502 |     def __create_M(self):
1503 |         self.num_variable = len(list(self.__variables.keys()))
1504 |         num_physical_dimensions = len(self.__fundamental_vars_used)
1505 |         if self.num_variable <= num_physical_dimensions:
1506 |             raise Exception('The number of variables has to be greater than the number of physical dimensions.')
1507 | 
1508 |         self.M = np.zeros(shape=(self.num_variable, num_physical_dimensions))
1509 |         # fill M
1510 |         for var_name in self.__variables.keys():
1511 |             expr = self.__variables[var_name]
1512 |             vect = self.__extract_exponents(expr)
1513 |             row = self.__idx_from_var[var_name]
1514 |             self.M[row, :] = vect
1515 | 
1516 |         self.M = self.M.transpose()
1517 | 
1518 |     def __create_symbolic_variables(self):
1519 |         for var_name in self.__variables.keys():
1520 |             self.__sym_variables[var_name] = sp.symbols(var_name)
1521 | 
1522 |     def __solve_null_spaces(self):
1523 |         if self.__flagged_var['selected']==True:
1524 |             self.__solve_null_spaces_for_flagged_variables()
1525 | 
1526 |         else:
1527 |             for idx in self.__var_from_idx.keys():
1528 |                 self.__flagged_var['var_name'] = self.__var_from_idx[idx]
1529 |                 self.__flagged_var['var_index'] = idx
1530 |                 self.__flagged_var['selected'] = True
1531 | 
1532 |                 self.__solve_null_spaces_for_flagged_variables()
1533 | 
1534 |     def __solve_null_spaces_for_flagged_variables(self):
1535 | 
1536 |         assert self.__flagged_var['selected']==True, " you need to select a variable to be explicit"
1537 | 
1538 |         n = self.num_variable
1539 |         m = len(self.__fundamental_vars_used)
1540 | 
1541 |         original_indicies = list(range(0, n))
1542 |         all_idx = original_indicies.copy()
1543 |         if self.__flagged_var['selected']:
1544 |             del all_idx[self.__flagged_var['var_index']]
1545 | 
1546 |         # print(all_idx)
1547 |         all_combs = list(combinations(all_idx,m))
1548 |         # print(all_combs)
1549 | 
1550 |         num_det_0 = 0
1551 |         for comb in all_combs:
1552 |             temp_comb = list(comb).copy()
1553 |             extra_vars = [i for i in original_indicies if i not in temp_comb ]
1554 |             b_ns = []
1555 |             for extra_var in extra_vars:
1556 |                 new_order = {}
1557 |                 temp_comb.append(extra_var)
1558 |                 A = self.M[:,temp_comb].copy()
1559 |                 for num,var_idx in enumerate(temp_comb):
1560 |                     new_order[num] =  self.__var_from_idx[var_idx]
1561 |                 B = sp.Matrix(A)
1562 |                 test_mat = B[:,:m]
1563 |                 if sp.det(test_mat) !=0:
1564 |                     ns = B.nullspace()[0]
1565 |                     b_ns.append({'order': new_order, 'power': ns.tolist()})
1566 | 
1567 |                 else:
1568 |                     num_det_0+=1
1569 |                 temp_comb = list(comb).copy()
1570 |             if b_ns: # if b_ns is not empty add it to the nullspaces list
1571 |                 self.__null_spaces.append(b_ns)
1572 |         # print("num of det 0 : ",num_det_0)
1573 | 
1574 |     def __construct_symbolic_pi_terms(self):
1575 |         self.__allpiterms = []
1576 |         for space in self.__null_spaces:
1577 |             spacepiterms = []
1578 |             for term in space:
1579 |                 expr = 1
1580 |                 idx = 0
1581 |                 for order,power in zip(term['order'].keys(),term['power']):
1582 |                     expr *= self.__sym_variables[term['order'][order]] ** sp.nsimplify(sp.Rational(power[0]))
1583 |                     idx += 1
1584 |                 spacepiterms.append(expr)
1585 |             # check for already existing pi terms in previous null-spaces
1586 |             already_exists = False
1587 |             for previouspiterms in self.__allpiterms:
1588 |                 if all(x in previouspiterms for x in spacepiterms):
1589 |                     already_exists = True
1590 |                     break
1591 |             if not already_exists:
1592 |                 self.__allpiterms.append(spacepiterms)
1593 | 
1594 |     def __rm_duplicated_powers(self):
1595 |         # this algorithm rely on the fact that the nullspace function
1596 |         # in sympy set one free variable to 1 and the all other to zero
1597 |         # then solve the system by back substitution.
1598 |         duplicate = []
1599 |         dummy_other_terms = self.__allpiterms.copy()
1600 |         for num_set, pi_set in enumerate(self.__allpiterms):
1601 |             dummy_other_terms.remove(pi_set)
1602 |             for num_other, other in enumerate(dummy_other_terms):
1603 |                 permutations_sets = permutations(pi_set)
1604 |                 for p_set in permutations_sets:
1605 |                     # create a permutation vector from the permutation set
1606 |                     p_V = sp.Matrix(list(p_set))
1607 |                     # create a vector from the other set of dimensionless groups that we are comparing to.
1608 |                     o_V = sp.Matrix(other)
1609 |                     # create an element wise inverse of the vector of dimensionless groups
1610 |                     o_V_inv = o_V.applyfunc(lambda x:x**(-1))
1611 | 
1612 |                     result = sp.matrix_multiply_elementwise(p_V, o_V)
1613 |                     # obtain the index of numerical value in the result vector.
1614 |                     # numerical values indicates that one dimensionless group is the inverse of the other group
1615 |                     # in this algorithm the numerical value will be equal to 1 (this is a result of the nullspace function in sympy)
1616 |                     idx_num_result = [x for x in range(len(p_set)) if isinstance(result[x,0],sp.Number)]
1617 |                     # also repeat the multiplication with the inverse vector
1618 |                     result_inv = sp.matrix_multiply_elementwise(p_V, o_V_inv)
1619 |                     # check for the index of the numerical values in the result vector
1620 |                     idx_num_result_inv = [x for x in range(len(p_set)) if isinstance(result_inv[x,0],sp.Number)]
1621 |                     # concatinate the indices into one list
1622 |                     all_indices = idx_num_result + idx_num_result_inv
1623 |                     # compare if the two vector are duplicates
1624 |                     if set(all_indices) == set(list(range(len(p_set)))):
1625 |                         duplicate.append(pi_set)
1626 | 
1627 |         # remove duplicates from the main dict of all pi terms
1628 |         for dup in duplicate:
1629 |             self.__allpiterms.remove(dup)
1630 |         return duplicate
1631 | 
1632 |     def generate_pi_terms(self):
1633 |         '''
1634 |         Generates all the possible pi terms
1635 |         '''
1636 |         self.__create_M()
1637 | 
1638 |         self.__create_symbolic_variables()
1639 | 
1640 |         self.__solve_null_spaces()
1641 | 
1642 |         self.__construct_symbolic_pi_terms()
1643 | 
1644 |         self.__rm_duplicated_powers()
1645 | 
1646 |     @property
1647 |     def pi_terms(self):
1648 |         '''
1649 |         :return: a list with all the symbolic dimensionless terms for all permutation of the dimensional Matrix M
1650 |         '''
1651 |         return self.__allpiterms
1652 | 
1653 | 
1654 |     def __Jupyter_print(self):
1655 |         ''' print the rendered Latex format in Jupyter cell'''
1656 |         for set_num, space in enumerate(self.__allpiterms):
1657 |             latex_str= '\\text{Set }'
1658 |             latex_str+='{}: \\quad'.format(set_num+1)
1659 |             for num, term in enumerate(space):
1660 |                 latex_str += '\\pi_{} = '.format(num+1)+sp.latex(term)
1661 |                 latex_str += '\\quad'
1662 |             display(Math(latex_str))
1663 |             display(Markdown('---'))
1664 | 
1665 |     def __tabulate_print(self,latex_string=False):
1666 |         ''' print the dimensionless sets in a tabulated format'''
1667 | 
1668 |         latex_form = []
1669 |         for pi_set in self.__allpiterms:
1670 |             latex_set = []
1671 |             for pi in pi_set:
1672 |                 if latex_string:
1673 |                     if latex_string:
1674 |                         latex_set.append(sp.latex(pi))
1675 |                     else:
1676 |                         latex_set.append(pi)
1677 |                 else:
1678 |                     latex_set.append(pi)
1679 |             latex_form.append(latex_set)
1680 | 
1681 |         num_of_pi_terms = len(latex_form[0])
1682 | 
1683 |         headers = ['sets']
1684 |         for num in range(num_of_pi_terms):
1685 |             headers.append('Pi {}'.format(num + 1))
1686 | 
1687 |         for num, set in enumerate(latex_form):
1688 |             set.insert(0, num + 1)
1689 | 
1690 |         print(tabulate(latex_form, headers=headers))
1691 | 
1692 |     def print_all(self, latex_string=False):
1693 |         '''
1694 |         print all the sets of dimensionless groups in latex or symbolic form.
1695 |         :latex_string: optional boolean. If set to True the function will print the latex string of the
1696 |                         dimensionless groups. if set to False the function will print the symbolic form of the
1697 |                         dimensionless groups.
1698 |         '''
1699 |         try:
1700 |             ''' Try to render the latex in Jupyter cell'''
1701 |             self.__Jupyter_print()
1702 |         except:
1703 |             ''' print the dimensionless sets in a tabulated format when in terminal session'''
1704 |             self.__tabulate_print(latex_string)
1705 | 
1706 | 1707 |
1708 |
1709 | 1710 | 1711 |
1712 |

Ancestors (in MRO)

1713 | 1717 |

Static methods

1718 | 1719 |
1720 |
1721 |

def __init__(

self)

1722 |
1723 | 1724 | 1725 | 1726 | 1727 |

Construct an instance of the BuckinghamPi theorem

1728 |
1729 | 1730 |
1731 |
def __init__(self):
1732 |     '''
1733 |     Construct an instance of the BuckinghamPi theorem
1734 |     '''
1735 |     self.__var_from_idx={}
1736 |     self.__idx_from_var = {}
1737 |     self.__variables={}
1738 |     self.__sym_variables={}
1739 |     self.__flagged_var = {'var_name':None, 'var_index':None,'selected':False}
1740 |     self.__null_spaces = []
1741 |     self.__fundamental_vars_used = [] # list of fundamental variables being used
1742 | 
1743 | 1744 |
1745 |
1746 | 1747 |
1748 | 1749 | 1750 |
1751 |
1752 |

def add_variable(

self, name, units, non_repeating=False)

1753 |
1754 | 1755 | 1756 | 1757 | 1758 |

Add variables to use for the pi-theorem 1759 | :param name: (string) name of the variable to be added 1760 | :param units: (string) expression of the independent physical variable expressed in terms of the k independent fundamental units. 1761 | :param non_repeating: (boolean) select a variable to belong to the non-repeating variables matrix. This will ensure that the selected variable 1762 | only shows up in one dimensionless group.

1763 |
1764 | 1765 |
1766 |
def add_variable(self, name: str, units: str, non_repeating=False):
1767 |     '''
1768 |     Add variables to use for the pi-theorem
1769 |     :param name: (string) name of the variable to be added
1770 |     :param units: (string) expression of the independent physical variable expressed in terms of the k independent fundamental units.
1771 |     :param non_repeating: (boolean) select a variable to belong to the non-repeating variables matrix. This will ensure that the selected variable
1772 |                                     only shows up in one dimensionless group.
1773 |     '''
1774 |     expr =  self.__parse_expression(units)
1775 |     self.__variables.update({name:expr})
1776 |     var_idx = len(list(self.__variables.keys()))-1
1777 |     self.__var_from_idx[var_idx]= name
1778 |     self.__idx_from_var[name] = var_idx
1779 |     if non_repeating and (self.__flagged_var['selected'] == False):
1780 |         self.__flagged_var['var_name'] = name
1781 |         self.__flagged_var['var_index'] = var_idx
1782 |         self.__flagged_var['selected'] = True
1783 |     elif non_repeating and (self.__flagged_var['selected'] == True):
1784 |         raise Exception("you cannot select more than one variable at a time to be a non_repeating.")
1785 | 
1786 | 1787 |
1788 |
1789 | 1790 |
1791 | 1792 | 1793 |
1794 |
1795 |

def generate_pi_terms(

self)

1796 |
1797 | 1798 | 1799 | 1800 | 1801 |

Generates all the possible pi terms

1802 |
1803 | 1804 |
1805 |
def generate_pi_terms(self):
1806 |     '''
1807 |     Generates all the possible pi terms
1808 |     '''
1809 |     self.__create_M()
1810 |     self.__create_symbolic_variables()
1811 |     self.__solve_null_spaces()
1812 |     self.__construct_symbolic_pi_terms()
1813 |     self.__rm_duplicated_powers()
1814 | 
1815 | 1816 |
1817 |
1818 | 1819 |
1820 | 1821 | 1822 |
1823 |
1824 |

def print_all(

self, latex_string=False)

1825 |
1826 | 1827 | 1828 | 1829 | 1830 |

print all the sets of dimensionless groups in latex or symbolic form. 1831 | :latex_string: optional boolean. If set to True the function will print the latex string of the 1832 | dimensionless groups. if set to False the function will print the symbolic form of the 1833 | dimensionless groups.

1834 |
1835 | 1836 |
1837 |
def print_all(self, latex_string=False):
1838 |     '''
1839 |     print all the sets of dimensionless groups in latex or symbolic form.
1840 |     :latex_string: optional boolean. If set to True the function will print the latex string of the
1841 |                     dimensionless groups. if set to False the function will print the symbolic form of the
1842 |                     dimensionless groups.
1843 |     '''
1844 |     try:
1845 |         ''' Try to render the latex in Jupyter cell'''
1846 |         self.__Jupyter_print()
1847 |     except:
1848 |         ''' print the dimensionless sets in a tabulated format when in terminal session'''
1849 |         self.__tabulate_print(latex_string)
1850 | 
1851 | 1852 |
1853 |
1854 | 1855 |
1856 | 1857 |

Instance variables

1858 |
1859 |

var fundamental_variables

1860 | 1861 | 1862 | 1863 | 1864 |

:return: a list of the fundamental variables being used

1865 |
1866 |
1867 | 1868 |
1869 |
1870 |

var pi_terms

1871 | 1872 | 1873 | 1874 | 1875 |

:return: a list with all the symbolic dimensionless terms for all permutation of the dimensional Matrix M

1876 |
1877 |
1878 | 1879 |
1880 |
1881 |

var variables

1882 | 1883 | 1884 | 1885 | 1886 |

:return: a dict of the variables added by the user.

1887 |
1888 |
1889 | 1890 |
1891 |
1892 |
1893 | 1894 |
1895 | 1896 |
1897 |
1898 |
1899 |

1900 | Documentation generated by 1901 | pdoc 0.3.2 1902 |

1903 | 1904 |

pdoc is in the public domain with the 1905 | UNLICENSE

1906 | 1907 |

Design by Kailash Nadh

1908 |
1909 |
1910 | 1911 | 1912 | -------------------------------------------------------------------------------- /doc/readme_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saadgroup/BuckinghamPy/1fa26797d8f74faff3af59011c11a661be1ff1e4/doc/readme_result.png -------------------------------------------------------------------------------- /examples.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "toc": "true" 7 | }, 8 | "source": [ 9 | "# Table of Contents\n", 10 | "

" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "# Buckingham $\\pi$: Dimensional Analysis\n", 18 | "**Mokbel Karam, and Prof. Tony Saad (www.tsaad.net)
Department of Chemical Engineering
University of Utah**\n", 19 | "
" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 1, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "from IPython.display import Image\n", 29 | "from IPython.core.display import HTML " 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "In this Jupyter notebook we will execute the code presented in the paper." 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "# Example 1: Pressure Drop in Pipe\n", 44 | "---" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 2, 50 | "metadata": { 51 | "scrolled": false 52 | }, 53 | "outputs": [ 54 | { 55 | "data": { 56 | "text/latex": [ 57 | "$$\\text{Set }1: \\quad\\pi_1 = \\frac{R}{d}\\quad\\pi_2 = \\frac{Q \\mu}{d^{3} {\\Delta}p}\\quad$$" 58 | ], 59 | "text/plain": [ 60 | "" 61 | ] 62 | }, 63 | "metadata": {}, 64 | "output_type": "display_data" 65 | }, 66 | { 67 | "data": { 68 | "text/markdown": [ 69 | "---" 70 | ], 71 | "text/plain": [ 72 | "" 73 | ] 74 | }, 75 | "metadata": {}, 76 | "output_type": "display_data" 77 | }, 78 | { 79 | "data": { 80 | "text/latex": [ 81 | "$$\\text{Set }2: \\quad\\pi_1 = \\frac{R \\sqrt[3]{{\\Delta}p}}{\\sqrt[3]{Q} \\sqrt[3]{\\mu}}\\quad\\pi_2 = \\frac{d \\sqrt[3]{{\\Delta}p}}{\\sqrt[3]{Q} \\sqrt[3]{\\mu}}\\quad$$" 82 | ], 83 | "text/plain": [ 84 | "" 85 | ] 86 | }, 87 | "metadata": {}, 88 | "output_type": "display_data" 89 | }, 90 | { 91 | "data": { 92 | "text/markdown": [ 93 | "---" 94 | ], 95 | "text/plain": [ 96 | "" 97 | ] 98 | }, 99 | "metadata": {}, 100 | "output_type": "display_data" 101 | }, 102 | { 103 | "data": { 104 | "text/latex": [ 105 | "$$\\text{Set }3: \\quad\\pi_1 = \\frac{d}{R}\\quad\\pi_2 = \\frac{Q \\mu}{R^{3} {\\Delta}p}\\quad$$" 106 | ], 107 | "text/plain": [ 108 | "" 109 | ] 110 | }, 111 | "metadata": {}, 112 | "output_type": "display_data" 113 | }, 114 | { 115 | "data": { 116 | "text/markdown": [ 117 | "---" 118 | ], 119 | "text/plain": [ 120 | "" 121 | ] 122 | }, 123 | "metadata": {}, 124 | "output_type": "display_data" 125 | } 126 | ], 127 | "source": [ 128 | "from buckinghampy import BuckinghamPi\n", 129 | "\n", 130 | "Pressure_Drop = BuckinghamPi()\n", 131 | "Pressure_Drop.add_variable(name='{\\\\Delta}p',dimensions='M*L^(-1)*T^(-2)') # pressure drop\n", 132 | "Pressure_Drop.add_variable(name='R',dimensions='L') # length of the pipe\n", 133 | "Pressure_Drop.add_variable(name='d',dimensions='L') # diameter of the pipe\n", 134 | "Pressure_Drop.add_variable(name='\\\\mu',dimensions='M*L^(-1)*T^(-1)') # viscosity\n", 135 | "Pressure_Drop.add_variable(name='Q',dimensions='L^(3)*T^(-1)') # volumetic flow rate\n", 136 | "\n", 137 | "Pressure_Drop.generate_pi_terms()\n", 138 | "Pressure_Drop.print_all()" 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "metadata": {}, 144 | "source": [ 145 | "# Example 2: Speed of Virus Infection\n", 146 | "---" 147 | ] 148 | }, 149 | { 150 | "cell_type": "markdown", 151 | "metadata": {}, 152 | "source": [ 153 | "Using mass, length, time and temperature as fundamental dimensions:" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 3, 159 | "metadata": {}, 160 | "outputs": [ 161 | { 162 | "data": { 163 | "text/latex": [ 164 | "$$\\text{Set }1: \\quad\\pi_1 = \\frac{P_{r}^{2} V_{p}}{C_{a}}\\quad\\pi_2 = \\frac{C_{a} C_{e}}{P_{r}^{3}}\\quad\\pi_3 = E_{fs} P_{r}^{2}\\quad$$" 165 | ], 166 | "text/plain": [ 167 | "" 168 | ] 169 | }, 170 | "metadata": {}, 171 | "output_type": "display_data" 172 | }, 173 | { 174 | "data": { 175 | "text/markdown": [ 176 | "---" 177 | ], 178 | "text/plain": [ 179 | "" 180 | ] 181 | }, 182 | "metadata": {}, 183 | "output_type": "display_data" 184 | }, 185 | { 186 | "data": { 187 | "text/latex": [ 188 | "$$\\text{Set }2: \\quad\\pi_1 = \\frac{C_{e} V_{p}}{P_{r}}\\quad\\pi_2 = \\frac{C_{a} C_{e}}{P_{r}^{3}}\\quad\\pi_3 = E_{fs} P_{r}^{2}\\quad$$" 189 | ], 190 | "text/plain": [ 191 | "" 192 | ] 193 | }, 194 | "metadata": {}, 195 | "output_type": "display_data" 196 | }, 197 | { 198 | "data": { 199 | "text/markdown": [ 200 | "---" 201 | ], 202 | "text/plain": [ 203 | "" 204 | ] 205 | }, 206 | "metadata": {}, 207 | "output_type": "display_data" 208 | }, 209 | { 210 | "data": { 211 | "text/latex": [ 212 | "$$\\text{Set }3: \\quad\\pi_1 = \\frac{C_{e}^{\\frac{2}{3}} V_{p}}{\\sqrt[3]{C_{a}}}\\quad\\pi_2 = \\frac{P_{r}}{\\sqrt[3]{C_{a}} \\sqrt[3]{C_{e}}}\\quad\\pi_3 = C_{a}^{\\frac{2}{3}} C_{e}^{\\frac{2}{3}} E_{fs}\\quad$$" 213 | ], 214 | "text/plain": [ 215 | "" 216 | ] 217 | }, 218 | "metadata": {}, 219 | "output_type": "display_data" 220 | }, 221 | { 222 | "data": { 223 | "text/markdown": [ 224 | "---" 225 | ], 226 | "text/plain": [ 227 | "" 228 | ] 229 | }, 230 | "metadata": {}, 231 | "output_type": "display_data" 232 | }, 233 | { 234 | "data": { 235 | "text/latex": [ 236 | "$$\\text{Set }4: \\quad\\pi_1 = \\frac{V_{p}}{C_{a} E_{fs}}\\quad\\pi_2 = \\sqrt{E_{fs}} P_{r}\\quad\\pi_3 = C_{a} C_{e} E_{fs}^{\\frac{3}{2}}\\quad$$" 237 | ], 238 | "text/plain": [ 239 | "" 240 | ] 241 | }, 242 | "metadata": {}, 243 | "output_type": "display_data" 244 | }, 245 | { 246 | "data": { 247 | "text/markdown": [ 248 | "---" 249 | ], 250 | "text/plain": [ 251 | "" 252 | ] 253 | }, 254 | "metadata": {}, 255 | "output_type": "display_data" 256 | }, 257 | { 258 | "data": { 259 | "text/latex": [ 260 | "$$\\text{Set }5: \\quad\\pi_1 = C_{e} \\sqrt{E_{fs}} V_{p}\\quad\\pi_2 = \\sqrt{E_{fs}} P_{r}\\quad\\pi_3 = C_{a} C_{e} E_{fs}^{\\frac{3}{2}}\\quad$$" 261 | ], 262 | "text/plain": [ 263 | "" 264 | ] 265 | }, 266 | "metadata": {}, 267 | "output_type": "display_data" 268 | }, 269 | { 270 | "data": { 271 | "text/markdown": [ 272 | "---" 273 | ], 274 | "text/plain": [ 275 | "" 276 | ] 277 | }, 278 | "metadata": {}, 279 | "output_type": "display_data" 280 | } 281 | ], 282 | "source": [ 283 | "from buckinghampy import BuckinghamPi\n", 284 | "\n", 285 | "Virus_Infection = BuckinghamPi()\n", 286 | "Virus_Infection.add_variable(name='V_{p}',dimensions='L*T^(-1)', non_repeating=True) # virus spread rate\n", 287 | "Virus_Infection.add_variable(name='P_{r}',dimensions='L') # precipitation\n", 288 | "Virus_Infection.add_variable(name='{\\\\theta}',dimensions='C') # temperature\n", 289 | "Virus_Infection.add_variable(name='C_{a}',dimensions='L^(3)/T') # airflow\n", 290 | "Virus_Infection.add_variable(name='C_{e}',dimensions='T') # seasonal changes\n", 291 | "Virus_Infection.add_variable(name='E_{fs}',dimensions='L^(-2)') # social structures\n", 292 | "Virus_Infection.add_variable(name='H',dimensions='M*L^(-3)') # humidity\n", 293 | "\n", 294 | "Virus_Infection.generate_pi_terms()\n", 295 | "Virus_Infection.print_all()" 296 | ] 297 | }, 298 | { 299 | "cell_type": "markdown", 300 | "metadata": {}, 301 | "source": [ 302 | "# Example 3: Economic Growth\n", 303 | "---" 304 | ] 305 | }, 306 | { 307 | "cell_type": "code", 308 | "execution_count": 4, 309 | "metadata": {}, 310 | "outputs": [ 311 | { 312 | "data": { 313 | "text/latex": [ 314 | "$$\\text{Set }1: \\quad\\pi_1 = \\frac{P r}{L {\\omega_{L}}}\\quad\\pi_2 = \\frac{Y}{L {\\omega_{L}}}\\quad\\pi_3 = \\frac{{\\delta}}{r}\\quad$$" 315 | ], 316 | "text/plain": [ 317 | "" 318 | ] 319 | }, 320 | "metadata": {}, 321 | "output_type": "display_data" 322 | }, 323 | { 324 | "data": { 325 | "text/markdown": [ 326 | "---" 327 | ], 328 | "text/plain": [ 329 | "" 330 | ] 331 | }, 332 | "metadata": {}, 333 | "output_type": "display_data" 334 | }, 335 | { 336 | "data": { 337 | "text/latex": [ 338 | "$$\\text{Set }2: \\quad\\pi_1 = \\frac{P {\\delta}}{L {\\omega_{L}}}\\quad\\pi_2 = \\frac{Y}{L {\\omega_{L}}}\\quad\\pi_3 = \\frac{r}{{\\delta}}\\quad$$" 339 | ], 340 | "text/plain": [ 341 | "" 342 | ] 343 | }, 344 | "metadata": {}, 345 | "output_type": "display_data" 346 | }, 347 | { 348 | "data": { 349 | "text/markdown": [ 350 | "---" 351 | ], 352 | "text/plain": [ 353 | "" 354 | ] 355 | }, 356 | "metadata": {}, 357 | "output_type": "display_data" 358 | }, 359 | { 360 | "data": { 361 | "text/latex": [ 362 | "$$\\text{Set }3: \\quad\\pi_1 = \\frac{P r}{Y}\\quad\\pi_2 = \\frac{L {\\omega_{L}}}{Y}\\quad\\pi_3 = \\frac{{\\delta}}{r}\\quad$$" 363 | ], 364 | "text/plain": [ 365 | "" 366 | ] 367 | }, 368 | "metadata": {}, 369 | "output_type": "display_data" 370 | }, 371 | { 372 | "data": { 373 | "text/markdown": [ 374 | "---" 375 | ], 376 | "text/plain": [ 377 | "" 378 | ] 379 | }, 380 | "metadata": {}, 381 | "output_type": "display_data" 382 | }, 383 | { 384 | "data": { 385 | "text/latex": [ 386 | "$$\\text{Set }4: \\quad\\pi_1 = \\frac{P {\\delta}}{Y}\\quad\\pi_2 = \\frac{L {\\omega_{L}}}{Y}\\quad\\pi_3 = \\frac{r}{{\\delta}}\\quad$$" 387 | ], 388 | "text/plain": [ 389 | "" 390 | ] 391 | }, 392 | "metadata": {}, 393 | "output_type": "display_data" 394 | }, 395 | { 396 | "data": { 397 | "text/markdown": [ 398 | "---" 399 | ], 400 | "text/plain": [ 401 | "" 402 | ] 403 | }, 404 | "metadata": {}, 405 | "output_type": "display_data" 406 | } 407 | ], 408 | "source": [ 409 | "from buckinghampy import BuckinghamPi\n", 410 | "\n", 411 | "Economic_Growth = BuckinghamPi()\n", 412 | "Economic_Growth.add_variable(name='P',dimensions='K', non_repeating=True) # capital\n", 413 | "Economic_Growth.add_variable(name='L',dimensions='Q/T') # labor per period of time\n", 414 | "Economic_Growth.add_variable(name='{\\\\omega_{L}}',dimensions='K/Q') # wages per labor\n", 415 | "Economic_Growth.add_variable(name='Y',dimensions='K/T') # profit per period of time\n", 416 | "Economic_Growth.add_variable(name='r',dimensions='1/T') # rental rate period of time\n", 417 | "Economic_Growth.add_variable(name='{\\\\delta}',dimensions='1/T') # depreciation rate\n", 418 | "\n", 419 | "Economic_Growth.generate_pi_terms()\n", 420 | "Economic_Growth.print_all()" 421 | ] 422 | }, 423 | { 424 | "cell_type": "markdown", 425 | "metadata": {}, 426 | "source": [ 427 | "# Example 4: Pressure Inside a Bubble\n", 428 | "---" 429 | ] 430 | }, 431 | { 432 | "cell_type": "markdown", 433 | "metadata": {}, 434 | "source": [ 435 | "Using mass, length and time as fundamental physical dimensions:" 436 | ] 437 | }, 438 | { 439 | "cell_type": "code", 440 | "execution_count": 5, 441 | "metadata": {}, 442 | "outputs": [ 443 | { 444 | "name": "stdout", 445 | "output_type": "stream", 446 | "text": [ 447 | "The number of variables has to be greater than the number of physical dimensions.\n" 448 | ] 449 | } 450 | ], 451 | "source": [ 452 | "from buckinghampy import BuckinghamPi\n", 453 | "\n", 454 | "Pressure_In_Bubble = BuckinghamPi()\n", 455 | "Pressure_In_Bubble.add_variable(name='{\\\\Delta}p',dimensions='M*L^(-1)*T^(-2)') # pressure \n", 456 | "Pressure_In_Bubble.add_variable(name='R',dimensions='L') # diameter\n", 457 | "Pressure_In_Bubble.add_variable(name='\\\\sigma',dimensions='M*T^(-2)') # surface tension\n", 458 | "try:\n", 459 | " Pressure_In_Bubble.generate_pi_terms()\n", 460 | " Pressure_In_Bubble.print_all()\n", 461 | "except Exception as e:\n", 462 | " print(e)" 463 | ] 464 | }, 465 | { 466 | "cell_type": "markdown", 467 | "metadata": {}, 468 | "source": [ 469 | "---" 470 | ] 471 | }, 472 | { 473 | "cell_type": "markdown", 474 | "metadata": {}, 475 | "source": [ 476 | "Using force and length as fundamental physical dimensions:" 477 | ] 478 | }, 479 | { 480 | "cell_type": "code", 481 | "execution_count": 6, 482 | "metadata": {}, 483 | "outputs": [ 484 | { 485 | "data": { 486 | "text/latex": [ 487 | "$$\\text{Set }1: \\quad\\pi_1 = \\frac{\\sigma}{R {\\Delta}p}\\quad$$" 488 | ], 489 | "text/plain": [ 490 | "" 491 | ] 492 | }, 493 | "metadata": {}, 494 | "output_type": "display_data" 495 | }, 496 | { 497 | "data": { 498 | "text/markdown": [ 499 | "---" 500 | ], 501 | "text/plain": [ 502 | "" 503 | ] 504 | }, 505 | "metadata": {}, 506 | "output_type": "display_data" 507 | } 508 | ], 509 | "source": [ 510 | "from buckinghampy import BuckinghamPi\n", 511 | "\n", 512 | "Pressure_In_Bubble = BuckinghamPi()\n", 513 | "Pressure_In_Bubble.add_variable(name='{\\\\Delta}p',dimensions='F*L^(-2)') # pressure \n", 514 | "Pressure_In_Bubble.add_variable(name='R',dimensions='L') # diameter\n", 515 | "Pressure_In_Bubble.add_variable(name='\\\\sigma',dimensions='F*L^(-1)') # surface tension\n", 516 | "\n", 517 | "Pressure_In_Bubble.generate_pi_terms()\n", 518 | "Pressure_In_Bubble.print_all()" 519 | ] 520 | } 521 | ], 522 | "metadata": { 523 | "kernelspec": { 524 | "display_name": "Python 3", 525 | "language": "python", 526 | "name": "python3" 527 | }, 528 | "language_info": { 529 | "codemirror_mode": { 530 | "name": "ipython", 531 | "version": 3 532 | }, 533 | "file_extension": ".py", 534 | "mimetype": "text/x-python", 535 | "name": "python", 536 | "nbconvert_exporter": "python", 537 | "pygments_lexer": "ipython3", 538 | "version": "3.6.8" 539 | }, 540 | "toc": { 541 | "colors": { 542 | "hover_highlight": "#DAA520", 543 | "navigate_num": "#000000", 544 | "navigate_text": "#333333", 545 | "running_highlight": "#FF0000", 546 | "selected_highlight": "#FFD700", 547 | "sidebar_border": "#EEEEEE", 548 | "wrapper_background": "#FFFFFF" 549 | }, 550 | "moveMenuLeft": true, 551 | "nav_menu": { 552 | "height": "49px", 553 | "width": "252px" 554 | }, 555 | "navigate_menu": true, 556 | "number_sections": false, 557 | "sideBar": true, 558 | "threshold": 4, 559 | "toc_cell": true, 560 | "toc_position": { 561 | "height": "39px", 562 | "left": "0px", 563 | "right": "1608px", 564 | "top": "136px", 565 | "width": "312px" 566 | }, 567 | "toc_section_display": "block", 568 | "toc_window_display": false, 569 | "widenNotebook": false 570 | }, 571 | "varInspector": { 572 | "cols": { 573 | "lenName": 16, 574 | "lenType": 16, 575 | "lenVar": 40 576 | }, 577 | "kernels_config": { 578 | "python": { 579 | "delete_cmd_postfix": "", 580 | "delete_cmd_prefix": "del ", 581 | "library": "var_list.py", 582 | "varRefreshCmd": "print(var_dic_list())" 583 | }, 584 | "r": { 585 | "delete_cmd_postfix": ") ", 586 | "delete_cmd_prefix": "rm(", 587 | "library": "var_list.r", 588 | "varRefreshCmd": "cat(var_dic_list()) " 589 | } 590 | }, 591 | "types_to_exclude": [ 592 | "module", 593 | "function", 594 | "builtin_function_or_method", 595 | "instance", 596 | "_Feature" 597 | ], 598 | "window_display": false 599 | } 600 | }, 601 | "nbformat": 4, 602 | "nbformat_minor": 4 603 | } 604 | -------------------------------------------------------------------------------- /generate-doc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | dir=doc 3 | if [ -d $dir ] 4 | then 5 | pdoc --html buckinghampy/buckinghampi.py --html-dir ./doc --overwrite 6 | else 7 | mkdir $dir 8 | pdoc --html buckinghampy/buckinghampi.py --html-dir ./doc 9 | fi -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.16.2 2 | sympy>=1.3 3 | tabulate>=0.8.9 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = buckinghampy 3 | version = 1.0.2 4 | description = Educational package for dimensional analysis 5 | url = https://github.com/saadgroup/BuckinghamPi 6 | author = Mokbel Karam 7 | author_email = mokbel.karam@chemeng.utah.edu 8 | license = MIT 9 | long_description = 10 | platforms = Linux, Mac OS X, Windows 11 | classifiers = 12 | Intended Audience :: Developers 13 | Intended Audience :: Education 14 | Intended Audience :: Science/Research 15 | Programming Language :: Python 16 | Programming Language :: Python :: 3 17 | 18 | [options] 19 | packages = find: 20 | python_requires = >=3.6 21 | install_requires = 22 | numpy>=1.16.2 23 | sympy>=1.3 24 | tabulate>=0.8.9 25 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | setup() 3 | --------------------------------------------------------------------------------