├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── configs ├── config_template.yml └── logging.yml ├── jupyter_notebooks ├── convergence.ipynb ├── interfacemetrics_LapRD.ipynb ├── interfacemetrics_adjacent_vicsweep.ipynb ├── interfacemetrics_conicalNozzle.ipynb ├── interfacemetrics_fusion.ipynb ├── interfacemetrics_viscsweep.ipynb ├── interfacemetrics_yielding.ipynb ├── noz3dscript.ipynb ├── point_sort.ipynb └── videoCombine.ipynb ├── paraviewscripts ├── __init__.py ├── colorBars.py ├── comboscript.py ├── paraview_csv.py ├── paraview_general.py ├── paraview_line.py ├── paraview_screenshots.py └── sim_metrics.py ├── py ├── __init__.py ├── add_units.py ├── cluster_tools │ ├── donescript.py │ ├── folder_parser.py │ └── foldermover.py ├── file │ ├── backwards_read.py │ ├── file_export.py │ ├── file_handling.py │ ├── file_names.py │ └── plainIm.py ├── folder_loop.py ├── folder_scraper.py ├── folder_stats.py ├── initialize │ ├── block.py │ ├── block_points.py │ ├── boundary_input.py │ ├── cd_vars.py │ ├── compile_0.py │ ├── compile_all_run.py │ ├── compile_block_mesh_dict.py │ ├── compile_control_dict.py │ ├── compile_dynamic_mesh_dict.py │ ├── compile_fv.py │ ├── compile_g.py │ ├── compile_mesh_quality_dict.py │ ├── compile_set_fields_dict.py │ ├── compile_snappy_hex_mesh_dict.py │ ├── compile_surface_feature_extract_dict.py │ ├── compile_surface_features_dict.py │ ├── compile_transport_properties.py │ ├── compile_turbulence_properties.py │ ├── creator.py │ ├── creator_adjacent.py │ ├── dict_list.py │ ├── export.py │ ├── file_creator.py │ ├── file_group.py │ ├── file_plotter.py │ ├── fluid.py │ ├── fv_sol_grp.py │ ├── fv_vars.py │ ├── geometry_file.py │ ├── initialize_tools.py │ ├── mesh_vars.py │ ├── ncreate3d.py │ ├── noz_vars.py │ ├── real_boundaries.py │ └── transport_group.py ├── plot │ ├── archive │ │ ├── README.txt │ │ ├── interface_plots.py │ │ ├── interfacemetrics.py │ │ ├── plot_conical.py │ │ ├── plot_folderscraper.py │ │ ├── plot_general.py │ │ ├── plot_line.py │ │ ├── plot_slices.py │ │ ├── plot_steady.py │ │ └── plot_survival.py │ ├── cells_plot.py │ ├── colors.py │ ├── combo_plot.py │ ├── convergence_plot.py │ ├── folder_plots.py │ ├── legend.py │ ├── markers.py │ ├── measurement_plot.py │ ├── meta_plot.py │ ├── multi_plot.py │ ├── pic_plot.py │ ├── plot_adjacent.py │ ├── plot_tools.py │ ├── rate_plot.py │ ├── sizes.py │ ├── super_summary_plot.py │ ├── time_plot.py │ ├── trace_plots.py │ ├── txt_plot.py │ ├── value_plot.py │ ├── var_plots.py │ └── xs_plot.py ├── points │ ├── folder_points.py │ ├── points_tools.py │ └── slice_points.py ├── scrape.py ├── scrape_tools.py ├── summarize │ ├── ideals.py │ ├── legend_summary.py │ ├── log_reader.py │ ├── steady.py │ ├── sum_and_steady.py │ ├── summarizer.py │ ├── summarizer_adjacent.py │ ├── summarizer_single.py │ └── super_summary.py ├── tools │ ├── config.py │ ├── logs.py │ ├── strings.py │ └── val_tools.py └── video │ └── video_funcs.py └── requirements.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /summaries/ 2 | *.pyc 3 | *.log 4 | *-checkpoint* 5 | *config.yml 6 | /pythonscripts/archive 7 | pythonscripts/momentumdiffusion.ipynb 8 | *cloudCheck.ipynb 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | NIST-developed software is provided by NIST as a public service. You may use, copy, and distribute copies of the software in any medium, provided that you keep intact this entire notice. You may improve, modify, and create derivative works of the software or any portion of the software, and you may copy and distribute such modifications or works. Modified works should carry a notice stating that you changed the software and should note the date and nature of any such change. Please explicitly acknowledge the National Institute of Standards and Technology as the source of the software. 2 | 3 | NIST-developed software is expressly provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED, IN FACT, OR ARISING BY OPERATION OF LAW, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND DATA ACCURACY. NIST NEITHER REPRESENTS NOR WARRANTS THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT ANY DEFECTS WILL BE CORRECTED. NIST DOES NOT WARRANT OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF THE SOFTWARE OR THE RESULTS THEREOF, INCLUDING BUT NOT LIMITED TO THE CORRECTNESS, ACCURACY, RELIABILITY, OR USEFULNESS OF THE SOFTWARE. 4 | 5 | You are solely responsible for determining the appropriateness of using and distributing the software and you assume all risks associated with its use, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and the unavailability or interruption of operation. This software is not intended to be used in any situation where a failure could cause risk of injury or damage to property. The software developed by NIST employees is not subject to copyright protection within the United States. -------------------------------------------------------------------------------- /configs/config_template.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | path: 4 | logs: "logs" 5 | log_config: "configs/logging.yml" 6 | c: "C:/myfolder/openFoamInputFiles" 7 | e: "E:/anotherLocalFolder" 8 | server: "//server.location/myfolder/simulationsStoredHere" 9 | fig: "C:/myfolder/storeFiguresHere" 10 | slurmFolder: '/working/myusername' # folder on the cluster where simulations are run 11 | legend_units: "myfolder/legend_units.csv" 12 | timing: 13 | abort1: 120 # abort at 1 second if the simulation rate exceeds 120 hours per simulation second 14 | abort2: 60 # abort at 2 seconds if the simulation rate exceeds 60 hours per simulation second 15 | -------------------------------------------------------------------------------- /configs/logging.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | version: 1 4 | # Set to False to get log messages from external packages you're using 5 | disable_existing_loggers: False 6 | 7 | # Formatters that are assigned to outputs ("handlers") below 8 | formatters: 9 | simple: 10 | format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 11 | 12 | # Outputs, one or more of which can be assigned to loggers 13 | handlers: 14 | console: 15 | class: logging.StreamHandler 16 | level: DEBUG 17 | formatter: simple 18 | stream: ext://sys.stdout 19 | 20 | debug_file_handler: 21 | class: logging.handlers.RotatingFileHandler 22 | level: DEBUG 23 | formatter: simple 24 | filename: logs/debug.log 25 | maxBytes: 20485760 # 20MB 26 | backupCount: 10 27 | encoding: utf8 28 | 29 | info_file_handler: 30 | class: logging.handlers.RotatingFileHandler 31 | level: INFO 32 | formatter: simple 33 | filename: logs/info.log 34 | maxBytes: 10485760 # 10MB 35 | backupCount: 10 36 | encoding: utf8 37 | 38 | # How is each logger handled? In particular, set external packages you're 39 | # using which spam the DEBUG, to log INFO and above (below, this is done 40 | # for the urllib3 and s3transfer loggers) 41 | loggers: 42 | urllib3: 43 | level: INFO 44 | handlers: [info_file_handler] 45 | 46 | s3transfer: 47 | level: INFO 48 | handlers: [info_file_handler] 49 | 50 | # The root logger 51 | root: 52 | level: DEBUG 53 | handlers: [console, debug_file_handler, info_file_handler] 54 | 55 | --- -------------------------------------------------------------------------------- /paraviewscripts/__init__.py: -------------------------------------------------------------------------------- 1 | # info 2 | __author__ = "Leanne Friedrich" 3 | __copyright__ = "This data is publicly available according to the NIST statements of copyright, fair use and licensing; see https://www.nist.gov/director/copyright-fair-use-and-licensing-statements-srd-data-and-software" 4 | __credits__ = ["Leanne Friedrich", "Ross Gunther"] 5 | __license__ = "NIST" 6 | __version__ = "1.1.0" 7 | __maintainer__ = "Leanne Friedrich" 8 | __email__ = "Leanne.Friedrich@nist.gov" 9 | __status__ = "Development" -------------------------------------------------------------------------------- /py/__init__.py: -------------------------------------------------------------------------------- 1 | # info 2 | __author__ = "Leanne Friedrich" 3 | __copyright__ = "This data is publicly available according to the NIST statements of copyright, fair use and licensing; see https://www.nist.gov/director/copyright-fair-use-and-licensing-statements-srd-data-and-software" 4 | __credits__ = ["Leanne Friedrich", "Ross Gunther"] 5 | __license__ = "NIST" 6 | __version__ = "1.2.0" 7 | __maintainer__ = "Leanne Friedrich" 8 | __email__ = "Leanne.Friedrich@nist.gov" 9 | __status__ = "Development" -------------------------------------------------------------------------------- /py/add_units.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Cleaning up paraview csv files''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import csv 8 | import logging 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | 15 | # logging 16 | logger = logging.getLogger(__name__) 17 | logger.setLevel(logging.DEBUG) 18 | 19 | 20 | ################################################################# 21 | 22 | def addUnits(csvfile:str): 23 | '''Add units to a csv file''' 24 | blist = [] 25 | with open(csvfile, 'r') as b: 26 | canreturn = True 27 | csv_reader = csv.reader(b) 28 | try: 29 | line1 = next(csv_reader) 30 | except: 31 | return 32 | if 'interfacePoints' in csvfile or 'line' in csvfile: 33 | headerdict = {'alpha.ink':'alpha', 'Points:0':'x', 'Points:1':'y', 'Points:2':'z',\ 34 | 'U:0':'vx', 'U:1':'vy', 'U:2':'vz', 'Time':'time', 'nu1':'nu_ink', 'nu2':'nu_sup',\ 35 | 'VectorGradient:0':'shearrate0','VectorGradient:1':'shearrate1','VectorGradient:2':'shearrate2',\ 36 | 'VectorGradient:3':'shearrate3','VectorGradient:4':'shearrate4','VectorGradient:5':'shearrate5',\ 37 | 'VectorGradient:6':'shearrate6','VectorGradient:7':'shearrate7','VectorGradient:8':'shearrate8' } 38 | header = [headerdict.get(h, h) for h in line1] 39 | if header==line1: 40 | canreturn = True 41 | else: 42 | line1=header 43 | canreturn = False 44 | blist.append(line1) 45 | try: 46 | line2 = next(csv_reader) 47 | except: 48 | return 49 | 50 | if 'interfacePoints' in csvfile or 'line' in csvfile: 51 | unitdict = {'time':'s', 'x':'m', 'y':'m', 'z':'m', \ 52 | 'vx':'m/s', 'vy':'m/s', 'vz':'m/s', 'alpha':'',\ 53 | 'nu1':'m^2/s', 'nu2':'m^2/s', 'nu_ink':'m^2/s', 'nu_sup':'m^2/s',\ 54 | 'p':'kg/(m*s^2)', 'p_rgh':'kg/(m*s^2)', 'rAU':'m^3*s/kg', 'arc_length':'m',\ 55 | 'shearrate0':'1/s','shearrate1':'1/s','shearrate2':'1/s',\ 56 | 'shearrate3':'1/s','shearrate4':'1/s','shearrate5':'1/s',\ 57 | 'shearrate6':'1/s','shearrate7':'1/s','shearrate8':'1/s' 58 | } 59 | if 'sliceSummaries' in csvfile: 60 | xu = 'mm' 61 | unitdict = {'x':xu, 'xbehind':xu, 'time':'s', 'centery':xu, 'centerz':xu, 'area':xu+'^2', 'maxheight':xu, 'maxwidth':xu, 'centeryn':'', 'centerzn':'', 'arean':'', 'maxheightn':'', 'maxwidthn':'', 'vertdisp':xu, 'vertdispn':'', 'aspectratio':'', 'speed':'mm/s', 'speeddecay':''} 62 | if 'steadyTimes' in csvfile: 63 | unitdict = {'x':'mm', 't0':'s', 'tf':'s'} 64 | if 'steadyPositions' in csvfile: 65 | unitdict = {'x0':'mm', 'xf':'mm', 't':'s'} 66 | unitline = [unitdict.get(s, '') for s in line1] 67 | blist.append(unitline) 68 | if line2==unitline: 69 | line3 = next(csv_reader) 70 | if not line3==line2 and canreturn: 71 | return # this table already has units, so we're done 72 | # otherwise, this table has two rows of units, and the second one needs to be ignored 73 | else: 74 | # this table does not have units 75 | blist.append(line2) 76 | blist = blist+list(csv_reader) 77 | with open(csvfile, 'w', newline='', encoding='utf-8') as b: 78 | writer = csv.writer(b) 79 | for row in blist: 80 | writer.writerow(row) 81 | logging.debug(csvfile) -------------------------------------------------------------------------------- /py/cluster_tools/donescript.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for moving folders between computers, servers, for OpenFOAM simulations of embedded 3D printing of single filaments. ''' 3 | 4 | # global packages 5 | import sys 6 | import os 7 | currentdir = os.path.dirname(os.path.realpath(__file__)) 8 | parentdir = os.path.dirname(currentdir) 9 | sys.path.append(parentdir) 10 | 11 | # local packages 12 | import foldermover as fm 13 | import folderparser as fp 14 | 15 | # logging 16 | LOGGERDEFINED = False 17 | LOGGERDEFINED = fp.openLog('donescript.log', LOGGERDEFINED) 18 | 19 | 20 | #------------------------------------------------------------------------------------------------- 21 | 22 | if len(sys.argv)>2: 23 | loopTime = float(sys.argv[2]) 24 | else: 25 | loopTime = 1 26 | 27 | fm.doneFolder(sys.argv[1], 2.5, loopTime=loopTime) 28 | -------------------------------------------------------------------------------- /py/cluster_tools/folder_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for handling files and folders used in OpenFOAM simulations of embedded 3D printing of single filaments. Written for OpenFOAM v1912 and OpenFOAM 8. folderparser identifies log files from interFoam, etc. and collects information into csv tables 3 | ''' 4 | 5 | # external packages 6 | import os, sys 7 | import numpy as np 8 | import re 9 | import csv 10 | import shutil 11 | import errno 12 | from typing import List, Dict, Tuple, Union, Any, TextIO 13 | from datetime import datetime 14 | import time 15 | import logging, platform, socket 16 | 17 | # local packages 18 | currentdir = os.path.dirname(os.path.realpath(__file__)) 19 | sys.path.append(currentdir) 20 | from tools.config import cfg 21 | from folder_stats import folderStats 22 | 23 | 24 | #------------------------------------------------------------------------------------------------- 25 | 26 | 27 | def mkdirif(path:str) -> int: 28 | '''make a directory if it doesn't exist 29 | path is a full path name 30 | returns 1 if error, returns 0 for no error''' 31 | try: 32 | os.mkdir(path) 33 | except OSError as e: 34 | return 1 35 | else: 36 | logging.info ("Created directory %s" % path) 37 | return 0 38 | 39 | 40 | def copy(src:str, dest:str) -> None: 41 | '''Copy directory src to directory dest. Both should be full folder paths''' 42 | try: 43 | shutil.copytree(src, dest) 44 | except OSError as e: 45 | # If the error was caused because the source wasn't a directory 46 | if e.errno == errno.ENOTDIR: 47 | shutil.copy(src, dest) 48 | else: 49 | logging.error('Directory not copied. Error: %s' % e) 50 | return 51 | 52 | 53 | ###################################### 54 | #------------------------------------------------------------------------------------------------- 55 | 56 | 57 | def modifyControlDict(folder:str, tend:float) -> int: 58 | '''change the endtime in the controlDict 59 | returns 0 if the dictionary was changed, 1 if not''' 60 | cfi = os.path.join(folder, 'case') 61 | if os.path.exists(cfi): 62 | cf = cfi 63 | else: 64 | cf = folder 65 | cdfile = os.path.join(cf, 'system', 'controlDict') 66 | cdfilenew = os.path.join(cf, 'system', 'controlDict2') 67 | if not os.path.exists(cdfile): 68 | return 1 69 | retval = 0 70 | # if the endtime is already at the value, abort this loop and delete the new file 71 | with open(cdfile, 'r') as fold: 72 | with open(cdfilenew, 'w') as fnew: 73 | for line in fold: 74 | if line.startswith('endTime'): 75 | linenew = 'endTime\t'+str(tend)+';\n' 76 | if linenew==line: 77 | retval = 1 78 | break 79 | else: 80 | line = linenew 81 | fnew.write(line) 82 | 83 | # if we changed the endtime, overwrite the old file 84 | if retval==0: 85 | os.remove(cdfile) 86 | os.rename(cdfilenew, cdfile) 87 | print('Set end time to '+str(tend)+' in '+cdfile) 88 | else: 89 | os.remove(cdfilenew) 90 | return retval 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /py/file/backwards_read.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for reading text files backwards. Replaces file_read_backwards''' 3 | 4 | # external packages 5 | import os 6 | 7 | # info 8 | # copied from https://stackoverflow.com/questions/2301789/how-to-read-a-file-in-reverse-order 9 | 10 | 11 | #------------------------- 12 | 13 | def fileReadBackwards(filename:str, buf_size=8192): 14 | """A generator that returns the lines of a file in reverse order. Encoding is not used, but makes this interchangeable with file_read_backwards""" 15 | with open(filename) as fh: 16 | segment = None 17 | offset = 0 18 | fh.seek(0, os.SEEK_END) 19 | file_size = remaining_size = fh.tell() 20 | while remaining_size > 0: 21 | offset = min(file_size, offset + buf_size) 22 | fh.seek(file_size - offset) 23 | buffer = fh.read(min(remaining_size, buf_size)) 24 | remaining_size -= buf_size 25 | lines = buffer.split('\n') 26 | # The first line of the buffer is probably not a complete line so 27 | # we'll save it and append it to the last line of the next buffer 28 | # we read 29 | if segment is not None: 30 | # If the previous chunk starts right from the beginning of line 31 | # do not concat the segment to the last line of new chunk. 32 | # Instead, yield the segment first 33 | if buffer[-1] != '\n': 34 | lines[-1] += segment 35 | else: 36 | yield segment 37 | segment = lines[0] 38 | for index in range(len(lines) - 1, 0, -1): 39 | if lines[index]: 40 | yield lines[index] 41 | # Don't yield None if the file was empty 42 | if segment is not None: 43 | yield segment -------------------------------------------------------------------------------- /py/file/file_export.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for importing files and csvs. Do not add pandas to this file, it will break paraview''' 3 | 4 | # external packages 5 | import os 6 | from typing import List, Dict, Tuple, Union, Any, TextIO 7 | import logging 8 | import numpy as np 9 | import csv 10 | 11 | # local packages 12 | 13 | # logging 14 | logger = logging.getLogger(__name__) 15 | logger.setLevel(logging.DEBUG) 16 | 17 | #---------------------------------------------- 18 | 19 | def plainImDict(fn:str, unitCol:int=-1, valCol:Union[int,list]=1) -> Tuple[dict,dict]: 20 | '''import values from a csv into a dictionary''' 21 | if type(valCol) is list: 22 | d = dict([[i,{}] for i in valCol]) 23 | else: 24 | d = {} 25 | u = {} 26 | with open(fn, newline='') as csvfile: 27 | reader = csv.reader(csvfile, delimiter=',', quotechar='|') 28 | for row in reader: 29 | # save all rows as class attributes 30 | title = row[0] 31 | if title in d: 32 | title = f'{title}_1' 33 | if unitCol>0: 34 | u[row[0]] = row[unitCol] 35 | if type(valCol) is int: 36 | if valCol==1: 37 | val = row[valCol] 38 | else: 39 | val = (','.join(row[valCol:])).replace('\"', '') 40 | d[row[0]]=tryfloat(val) 41 | elif type(valCol) is list: 42 | for i in valCol: 43 | d[i][row[0]]=tryfloat(row[i]) 44 | return d,u 45 | 46 | def plainExpDict(fn:str, vals:dict, units:dict={}, diag:bool=True, quotechar:str='|') -> None: 47 | '''export the dictionary to file''' 48 | with open(fn, 'w', newline='') as csvfile: 49 | writer = csv.writer(csvfile, delimiter=',', quotechar=quotechar, quoting=csv.QUOTE_MINIMAL) 50 | for st,val in vals.items(): 51 | if st in units: 52 | row = [st, units[st], val] 53 | else: 54 | if len(units)>0: 55 | row = [st, '', val] 56 | else: 57 | row = [st, val] 58 | writer.writerow(row) 59 | if diag: 60 | logging.info(f'Exported {fn}') 61 | 62 | def exportFile(folder:str, file:str, text:str) -> None: 63 | '''exportFile exports a text file 64 | folder is the folder to save the file to (e.g. 'C:\\...\\myfolder') 65 | file is a file base name (e.g. 'myfile.txt') within that folder 66 | text is the text to write to the file''' 67 | fn = os.path.join(folder, file) 68 | File_object = open(fn,"w") 69 | File_object.write(text) 70 | File_object.close() 71 | logging.info("Exported file %s" % fn) 72 | 73 | 74 | def exportCSV(fn:str, table:List[Any]) -> None: 75 | '''exportCSV exports a csv file 76 | fn is the full path of the file to export 77 | table is the table to export, as a list''' 78 | with open(fn, mode='w', newline='') as f: 79 | w = csv.writer(f, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) 80 | for i in table: 81 | w.writerow(i) 82 | logging.info('Exported file %s' % fn) 83 | -------------------------------------------------------------------------------- /py/file/file_names.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Handling file names''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import csv 8 | import numpy as np 9 | import pandas as pd 10 | from shapely.geometry import Polygon 11 | import re 12 | from typing import List, Dict, Tuple, Union, Any 13 | import logging 14 | import traceback 15 | 16 | # local packages 17 | currentdir = os.path.dirname(os.path.realpath(__file__)) 18 | parentdir = os.path.dirname(currentdir) 19 | sys.path.append(parentdir) 20 | from tools.strings import varNicknames 21 | 22 | # logging 23 | logger = logging.getLogger(__name__) 24 | logger.setLevel(logging.DEBUG) 25 | 26 | 27 | ################################################################# 28 | 29 | 30 | class fnCreator: 31 | '''Construct an image file name with no extension. 32 | Exportfolder is the folder to export to. 33 | Label is any given label. 34 | Topfolder is the folder this image refers to, e.g. HBHBsweep. Insert any extra values in kwargs as keywords''' 35 | 36 | def __init__(self, exportFolder:str 37 | , labels:Union[list, str] 38 | , topFolder:str 39 | , titleVars:dict={} 40 | , **kwargs) -> str: 41 | self.vn = varNicknames() 42 | self.figTitle = '' 43 | if type(labels) is list: 44 | for i,label in enumerate(labels): 45 | if i==0: 46 | self.s = f'{label}' 47 | else: 48 | self.s = f'{self.s}_{label}' 49 | else: 50 | self.s = f'{labels}' 51 | 52 | if type(topFolder) is list: 53 | self.bn = ','.join([os.path.basename(tf) for tf in topFolder]) 54 | else: 55 | self.bn = os.path.basename(topFolder) 56 | self.s = f'{self.s}_{self.bn}' 57 | for key,val in kwargs.items(): 58 | self.addToStack(key,val) 59 | self.addToFigTitle(key,val) 60 | for key,val in titleVars.items(): 61 | self.addToFigTitle(key,val) 62 | 63 | self.s = self.s.replace('*', 'x') 64 | self.s = self.s.replace('/', 'div') 65 | self.s = self.vn.shorten(self.s) 66 | self.fn = os.path.join(exportFolder, self.bn, 'plots', self.s) 67 | if len(self.fn)>252: 68 | self.fn = self.fn[:252] 69 | 70 | def addToFigTitle(self, key:str, val:Any) -> str: 71 | '''get a figure title to describe the restrictions placed on this plot''' 72 | if '_list' in key: 73 | ki = self.vn.shortSymbol(key[:-5]) 74 | vi = ', '.join([self.vn.shortSymbol(str(i)) for i in val]) 75 | if len(self.figTitle)>0: 76 | self.figTitle = f'{self.figTitle}; {ki}: {vi}' 77 | else: 78 | self.figTitle = f'{ki}: {vi}' 79 | if key=='restrictions': 80 | for keyi, vali in val.items(): 81 | self.addToFigTitle(f'{keyi}_list', vali) 82 | 83 | 84 | def addToStack(self, key:str, val:Any) -> str: 85 | '''add a variable definition to the name''' 86 | if key in ['adjustBounds' 87 | , 'cname', 'colorDict', 'colorList', 'crops' 88 | , 'display','dispUnits' 89 | , 'ef', 'eps', 'export' 90 | , 'gridlines' 91 | , 'horizLabels' 92 | , 'insideLabels' 93 | , 'legendCols','line', 'lineDict' 94 | , 'makeLegend', 'markerDict', 'markerList' 95 | , 'overwrite' 96 | ,'plotType', 'png' 97 | , 'subLabels', 'split', 'svg' 98 | , 'xr', 'yr', 'xticks', 'yticks']: 99 | return '' 100 | if key=='restrictions': 101 | self.s = f'{self.s}_' 102 | else: 103 | self.s = f'{self.s}_{key}_' 104 | if type(val) is list: 105 | for ki in val: 106 | self.s = f'{self.s}{ki}-' 107 | self.s = self.s[0:-1] # remove last dash 108 | elif type(val) is dict: 109 | for key0,val0 in val.items(): 110 | if os.path.isdir(key0): 111 | self.s = f'{self.s}{os.path.basename(key0)}_' 112 | else: 113 | self.s = f'{self.s}{key0}_' 114 | if type(val0) is list: 115 | for ki in val0: 116 | self.s = f'{self.s}{ki}-' 117 | self.s = self.s[0:-1] # remove last dash 118 | else: 119 | if os.path.isdir(val0): 120 | self.s = f'{self.s}{os.path.basename(val0)}' 121 | else: 122 | self.s = f'{self.s}{val0}' 123 | self.s = f'{self.s}_' 124 | self.s = self.s[:-1] 125 | else: 126 | self.s = f'{self.s}{val}' 127 | self.s = self.s.replace(' ', '') 128 | 129 | def makeNew(self, export:bool=False, overwrite:bool=False) -> bool: 130 | '''determine if we should make a new file''' 131 | if overwrite: 132 | return True 133 | if not export: 134 | return True 135 | if os.path.exists(self.png): 136 | return 137 | 138 | def png(self): 139 | return f'{self.fn}.png' 140 | 141 | def svg(self): 142 | return f'{self.fn}.svg' 143 | 144 | def eps(self): 145 | return f'{self.fn}.eps' 146 | 147 | def saveFig(self, fig, fn:str) -> None: 148 | fig.savefig(fn, bbox_inches='tight', dpi=300, transparent=True) 149 | 150 | def export(self, fig, svg:bool=True, png:bool=True, eps:bool=False, **kwargs): 151 | '''export the png and/or svg''' 152 | if not png and not svg: 153 | return 154 | create = self.fn 155 | clist = [] 156 | while not os.path.exists(os.path.dirname(create)): 157 | clist.append(os.path.basename(create)) 158 | create = os.path.dirname(create) 159 | while len(clist)>0: 160 | os.mkdir(create) 161 | logging.info(f'Created directory {create}') 162 | create = os.path.join(create, clist.pop(-1)) 163 | if svg: 164 | self.saveFig(fig, self.svg()) 165 | if png: 166 | self.saveFig(fig, self.png()) 167 | if eps: 168 | self.saveFig(fig, self.eps()) 169 | logging.info(f'Exported {self.fn}') 170 | -------------------------------------------------------------------------------- /py/file/plainIm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for importing csv''' 3 | 4 | # external packages 5 | import os 6 | import pandas as pd 7 | from typing import List, Dict, Tuple, Union, Any, TextIO 8 | import logging 9 | import numpy as np 10 | import csv 11 | 12 | # local packages 13 | 14 | 15 | # logging 16 | logger = logging.getLogger(__name__) 17 | logger.setLevel(logging.DEBUG) 18 | 19 | 20 | 21 | #---------------------------------------------- 22 | 23 | def plainIm(file:str, ic:Union[int, bool]=0, checkUnits:bool=True) -> Tuple[Union[pd.DataFrame, List[Any]], Dict]: 24 | '''import a csv to a pandas dataframe. ic is the index column. Int if there is an index column, False if there is none. checkUnits=False to assume that there is no units row. Otherwise, look for a units row''' 25 | if os.path.exists(file): 26 | try: 27 | toprows = pd.read_csv(file, index_col=ic, nrows=2) 28 | 29 | toprows = toprows.fillna('') 30 | toprows.columns = map(str.lower, toprows.columns) # set headers to lowercase 31 | row1 = list(toprows.iloc[0]) 32 | if checkUnits and all([(type(s) is str or pd.isnull(s)) for s in row1]): 33 | # row 2 is all str: this file has units 34 | unitdict = dict(toprows.iloc[0]) 35 | skiprows=[1] 36 | else: 37 | unitdict = dict([[s,'undefined'] for s in toprows]) 38 | skiprows = [] 39 | try: 40 | d = pd.read_csv(file, index_col=ic, dtype=float, skiprows=skiprows) 41 | except: 42 | d = pd.read_csv(file, index_col=ic, skiprows=skiprows) 43 | except Exception as e: 44 | # logging.error(str(e)) 45 | return [],{} 46 | d.columns = map(str.lower, d.columns) # set headers to lowercase 47 | return d, unitdict 48 | else: 49 | return [], {} 50 | 51 | 52 | def plainExp(fn:str, data:pd.DataFrame, units:dict) -> None: 53 | '''export the file''' 54 | if len(data)==0 or len(units)==0: 55 | return 56 | col = pd.MultiIndex.from_tuples([(k,units[k]) for k in data]) # index with units 57 | data = np.array(data) 58 | df = pd.DataFrame(data, columns=col) 59 | df.to_csv(fn) 60 | logging.info(f'Exported {fn}') 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /py/folder_loop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for handling files''' 3 | 4 | # external packages 5 | import os, sys 6 | import re 7 | import shutil 8 | import time 9 | from typing import List, Dict, Tuple, Union, Any, TextIO 10 | import logging 11 | import pandas as pd 12 | import subprocess 13 | import time 14 | import traceback 15 | 16 | # local packages 17 | currentdir = os.path.dirname(os.path.realpath(__file__)) 18 | sys.path.append(currentdir) 19 | sys.path.append(os.path.dirname(currentdir)) 20 | from tools.config import cfg 21 | import file_handling as fh 22 | from file.plainIm import * 23 | 24 | # logging 25 | logger = logging.getLogger(__name__) 26 | logger.setLevel(logging.DEBUG) 27 | 28 | #---------------------------------------------- 29 | 30 | class folderLoop: 31 | '''loops a function over all printFolders in the topFolder. 32 | the function needs to have only one arg, folder, and all other variables need to go in kwargs 33 | folders could be either the top folder to recurse into, or a list of folders''' 34 | 35 | def __init__(self, folders:Union[str, list], func, printTraceback:bool=False, printErrors:bool=True, folderDiag:int=0, **kwargs): 36 | if type(folders) is list: 37 | # list of specific folders 38 | self.folders = [] 39 | for folder in folders: 40 | self.folders = self.folders + fh.simFolders(folder) 41 | elif not os.path.exists(folders): 42 | self.topFolder = '' 43 | self.folders = [] 44 | else: 45 | # top folder, recurse 46 | self.topFolder = folders 47 | self.folders = fh.simFolders(folders) 48 | self.func = func 49 | self.kwargs = kwargs 50 | self.printTraceback = printTraceback 51 | self.printErrors = printErrors 52 | self.folderDiag = folderDiag 53 | 54 | def runFolder(self, folder:str) -> None: 55 | '''run the function on one folder''' 56 | if self.folderDiag>0: 57 | print(folder) 58 | try: 59 | self.func(folder, **self.kwargs) 60 | except KeyboardInterrupt as e: 61 | raise e 62 | except Exception as e: 63 | self.folderErrorList.append({'folder':folder, 'error':e}) 64 | if self.printErrors: 65 | print(e) 66 | if self.printTraceback: 67 | traceback.print_exc() 68 | 69 | 70 | def run(self) -> list: 71 | '''apply the function to all folders''' 72 | self.folderErrorList = [] 73 | for folder in self.folders: 74 | self.runFolder(folder) 75 | return self.folderErrorList 76 | 77 | def testFolderError(self, i:int, **kwargs) -> None: 78 | '''test a single file that threw an error. i is the row in self.folderErrorList''' 79 | if i>len(self.folderErrorList): 80 | print(f'{i} is greater than number of error files ({len(self.folderErrorList)})') 81 | return 82 | row = self.folderErrorList[i] 83 | print(row) 84 | self.func(row['folder'], **kwargs) 85 | 86 | def exportErrors(self, fn:str) -> None: 87 | '''export the error list to file''' 88 | plainExp(fn, pd.DataFrame(self.folderErrorList), {'folder':'', 'error':''}, index=False) 89 | -------------------------------------------------------------------------------- /py/initialize/block.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | 9 | # local packages 10 | 11 | 12 | # logging 13 | logging.basicConfig(level=logging.INFO) 14 | 15 | 16 | 17 | #------------------------------------------------------------------------------------------------- 18 | 19 | class Block: 20 | '''stores information about a block during blockMesh ''' 21 | 22 | def __init__(self): 23 | self.vertices = [] 24 | self.meshi = [1,1,1] 25 | self.grading = [1,1,1] -------------------------------------------------------------------------------- /py/initialize/block_points.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import numpy as np 9 | 10 | # local packages 11 | 12 | # logging 13 | logging.basicConfig(level=logging.INFO) 14 | 15 | #------------------------------------------------------------------------------------------------- 16 | 17 | def ptFromPts(pts:np.array, x:float, y:float, z:float) -> List[float]: 18 | '''select the point (x,y,z) from an array of points pts 19 | Input: pts, x, y, z''' 20 | pt = pts[(pts[:,0]==x) & (pts[:,1]==y) & (pts[:,2]==z), :] 21 | return pt[0] 22 | 23 | 24 | def blockPts(pl:float, corner:List[float]) -> np.array: 25 | '''select the points in this block based on the first corner, and put the points in the order that openFOAM likes 26 | pl is an array containing points 27 | corner is one point which marks the first corner of the block''' 28 | xlist = np.unique(pl[:,0]) 29 | ylist = np.unique(pl[:,1]) 30 | zlist = np.unique(pl[:,2]) 31 | cx = np.where(xlist==corner[0]) 32 | cy = np.where(ylist==corner[1]) 33 | cz = np.where(zlist==corner[2]) 34 | cx = cx[0][0] 35 | cy = cy[0][0] 36 | cz = cz[0][0] 37 | pts = np.zeros([8, pl[1,:].size]) 38 | for k in [0,1]: 39 | pts[0+4*k, :] = ptFromPts(pl, xlist[cx], ylist[cy], zlist[cz+k]) 40 | pts[1+4*k, :] = ptFromPts(pl, xlist[cx+1], ylist[cy], zlist[cz+k]) 41 | pts[2+4*k, :] = ptFromPts(pl, xlist[cx+1], ylist[cy+1], zlist[cz+k]) 42 | pts[3+4*k, :] = ptFromPts(pl, xlist[cx], ylist[cy+1], zlist[cz+k]) 43 | return pts 44 | 45 | -------------------------------------------------------------------------------- /py/initialize/boundary_input.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | # local packages 9 | 10 | 11 | # logging 12 | logging.basicConfig(level=logging.INFO) 13 | 14 | 15 | 16 | #------------------------------------------------------------------------------------------------- 17 | 18 | 19 | class BoundaryInput: 20 | '''# stores information about a boundary ''' 21 | 22 | def __init__(self, labelin:str, typin:str): 23 | '''Input: labelin is the name of the boundary e.g. outerWall. typin is the boundary type, e.g. patch''' 24 | self.label = labelin # name of the boundary e.g. outerWall 25 | self.typ = typin # name of boundary type e.g. patch for blockMeshDict 26 | self.flist = [] # list of faces, which are lists of point indices 27 | self.alphalist = [] # list of alpha.ink properties 28 | self.Ulist = [] # list of U properties 29 | self.plist = [] # list of p_rgh properties 30 | self.meshi = [] # mesh for exporting to stl 31 | self.reflev = 0 -------------------------------------------------------------------------------- /py/initialize/cd_vars.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | 9 | # local packages 10 | 11 | # logging 12 | logging.basicConfig(level=logging.INFO) 13 | 14 | #------------------------------------------------------------------------------------------------- 15 | 16 | 17 | class CDVars: 18 | '''controlDict variables ''' 19 | 20 | def __init__(self, startTime:float, endTime:float, dt:float, writeDt:float): 21 | '''Inputs: startTime, endTime, dt, writeDt 22 | startTime is in seconds 23 | endTime is in seconds 24 | dt is the initial time step of the simulation in seconds 25 | writeDt is the time step at which to write results to file''' 26 | # solver 27 | self.application = "interFoam" 28 | 29 | # time control 30 | self.startFrom = "latestTime" 31 | self.startTime = startTime 32 | self.stopAt = "endTime" 33 | self.endTime = endTime 34 | self.deltaT = dt # Time step of the simulation 35 | 36 | # time step control 37 | self.adjustTimeStep = "yes" # yes/no† to adjust time step according to maximum Courant number in transient simulation. 38 | self.maxCo = 1 # Maximum Courant number allowed. 39 | self.maxAlphaCo = 1 40 | self.maxDeltaT = 1 # Maximum time step allowed in transient simulation 41 | 42 | # data writing 43 | #self.writeControl = "adjustable" # Writes data every writeInterval seconds of simulated time. 44 | self.writeControl = "adjustableRunTime" 45 | self.writeInterval = writeDt # Scalar used in conjunction with writeControl described above. 46 | self.purgeWrite = 0 # Integer representing a limit on the number of time directories that are stored by overwriting time directories on a cyclic basis. Example of t0 = 5s, Δt = 1s and purgeWrite 2;: data written into 2 directories, 6 and 7, before returning to write the data at 8 s in 6, data at 9 s into 7, etc. To disable the time directory limit, specify purgeWrite 0;† For steady-state solutions, results from previous iterations can be continuously overwritten by specifying purgeWrite 1; 47 | self.writeFormat = "ascii" # ASCII format, written to writePrecision significant figures. 48 | self.writePrecision = 6 # Integer used in conjunction with writeFormat described above, 6† by default 49 | self.writeCompression = "uncompressed" # Specifies the compression of the data files. 50 | self.timeFormat = "general" # Specifies scientific format if the exponent is less than -4 or greater than or equal to that specified by timePrecision. 51 | self.timePrecision = 6 # Integer used in conjunction with timeFormat described above, 6† by default 52 | 53 | # data reading 54 | self.runTimeModifiable = "yes" # yes†/no switch for whether dictionaries, e.g.controlDict, are re-read by OpenFOAM at the beginning of each time step. 55 | 56 | 57 | def varList(self) -> List[List[str]]: 58 | '''Constructs a table with all of the variables held in this object. The table has two columns: the variable name and the value''' 59 | l = [] 60 | for attr, value in self.__dict__.items(): 61 | l.append([attr, value]) 62 | return l -------------------------------------------------------------------------------- /py/initialize/compile_0.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from boundary_input import BoundaryInput 15 | from dict_list import DictList 16 | from initialize_tools import OpenFOAMFile 17 | 18 | # logging 19 | logging.basicConfig(level=logging.INFO) 20 | 21 | #------------------------------------------------------------------------------------------------- 22 | 23 | class compiler0(OpenFOAMFile): 24 | '''puts files in the 0 folder into the correct format 25 | classn is the class that goes into the header, e.g. "volScalarField" or "pointScalarField" 26 | obj is the object that goes into the header, e.g. "U" 27 | dims is the dimensions of the field, e.g. "[1 -1 -2 0 0 0 0]", which means kg/(m*s^2) 28 | intfield is the internal field, e.g. "0" or "(0 0 0)" 29 | # f is the function to run on each boundaryInput object to get a list of properties of interest''' 30 | 31 | def __init__(self, bl:List[BoundaryInput], classn:str, obj:str, dims:str, intfield:str, f:Callable): 32 | super().__init__() 33 | s = self.header(classn, obj) # header 34 | simplelist = DictList("", 0, [["dimensions", dims], ["internalField", "uniform " + intfield]]) # list of simple variables to define 35 | s = s + simplelist.prnt(-1) # format the simple list 36 | bflist = DictList("boundaryField", 0, []) # list of boundary dictionary entries 37 | for b in bl: 38 | bflist.proplist.append(f(b)) # add the appropriate list for each boundary, e.g. alpha properties for inkFlow 39 | s = s + bflist.prnt(0) # format the dictionary list 40 | s = s + self.closeLine() 41 | self.s = s 42 | 43 | class compileAlphaOrig(compiler0): 44 | 45 | def __init__(self, bl:List[BoundaryInput]) -> str: 46 | super().__init__(bl, "volScalarField", "alpha.ink", "[0 0 0 0 0 0 0]", "0", lambda bi:bi.alphalist) 47 | 48 | class compileU(compiler0): 49 | 50 | def __init__(self, bl:List[BoundaryInput]) -> str: 51 | super().__init__(bl, "volVectorField", "U", "[0 1 -1 0 0 0 0]", "(0.01 0 0)", lambda bi:bi.Ulist) 52 | 53 | class compileP(compiler0): 54 | 55 | def __init__(self, bl:List[BoundaryInput]) -> str: 56 | super().__init__(bl, "volScalarField", "p_rgh", "[1 -1 -2 0 0 0 0]", "0", lambda bi:bi.plist) 57 | 58 | class compileCellLevel(compiler0): 59 | 60 | def __init__(self, bl:List[BoundaryInput]) -> str: 61 | super().__init__(bl, "volScalarField", "cellLevel", "[0 0 0 0 0 0 0]", "0", lambda bi:[["type", "zeroGradient"]]) 62 | 63 | class compilePointLevel(compiler0): 64 | 65 | def __init__(self, bl:List[BoundaryInput]) -> str: 66 | super().__init__(bl, "pointScalarField", "pointLevel", "[0 0 0 0 0 0 0]", "0", lambda bi:[["type", "calculated"]]) -------------------------------------------------------------------------------- /py/initialize/compile_all_run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | # external packages 5 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 6 | import logging 7 | import os 8 | import numpy as np 9 | 10 | # local packages 11 | 12 | 13 | # logging 14 | logging.basicConfig(level=logging.INFO) 15 | 16 | #------------------------------------------------------------------------------------------------- 17 | 18 | class bashCompiler: 19 | '''class for bash files''' 20 | 21 | def __init__(self): 22 | return 23 | 24 | def opening(self): 25 | s = self.binbash() 26 | s = s + 'cd \"$(dirname \"$0\")\"\n' 27 | return s 28 | 29 | def binbash(self): 30 | return "#!/bin/bash\n\n" 31 | 32 | def fListLoop(self, s:str, functionlist:List[str], folder:str, ifstarted:bool=False) -> str: 33 | '''Write a bash script to go through multiple functions. ifstarted True if you do not want to run this function if the sim has started''' 34 | for f in functionlist: 35 | s1 = f'echo \"running {f} in {os.path.basename(folder)}\";\n{f}>>log_{f}' 36 | if ifstarted: 37 | s = f'{s}[ ! d \"0.1\"] && ({s1}); ' 38 | else: 39 | s = f'{s}{s1};\n' 40 | return s 41 | 42 | 43 | class compileAllClean(bashCompiler): 44 | '''this removes residual data from previous runs''' 45 | 46 | def __init__(self, endTime:float, writeDt:float): 47 | super().__init__() 48 | s = self.opening() 49 | s = s + 'rm -f -r 0.* ' 50 | for t in range(1, int(np.ceil(endTime+writeDt)+1), 1): 51 | s = f'{s}{t}* ' 52 | s = s + "slurm*.out log_*" 53 | self.s = s 54 | 55 | class compileAllAllClean(bashCompiler): 56 | '''for folders that contain both a mesh and case folder, create a function that runs both allrun functions''' 57 | 58 | def __init__(self): 59 | super().__init__() 60 | s = self.opening() 61 | s = s + 'rm -f -r slurm*.out log_*\n' 62 | s = s + ("./case/Allclean.sh") 63 | self.s = s 64 | 65 | 66 | class compileAllAllRun(bashCompiler): 67 | '''for folders that contain both a mesh and case folder, create a function that runs both allrun functions''' 68 | 69 | def __init__(self): 70 | super().__init__() 71 | s = self.opening() 72 | s = s + ("./mesh/Allrun.sh; ./case/Allrun.sh") 73 | self.s = s 74 | 75 | class compileAllRun(bashCompiler): 76 | '''this is the allrun bash script for the case folder''' 77 | 78 | def __init__(self, folder:str, solver:str): # RG 79 | super().__init__() 80 | f = os.path.basename(folder) 81 | s = self.opening() 82 | s = s + '. $WM_PROJECT_DIR/bin/tools/RunFunctions;\n' 83 | s = s + f'echo {f}\n' 84 | s = s + 'if [ ! -d "0.1" ]; then\n' 85 | s = s + '\tcp -r ../mesh/constant/polyMesh constant;\n' 86 | s = s + '\tcp 0/alpha.ink.orig 0/alpha.ink;\n' 87 | s = s + f'\techo \"running setFields in {f}\";\n' 88 | s = s + '\tsetFields>>log_setFields;\n' 89 | s = s + 'fi \n' 90 | s = self.fListLoop(s, [solver, "foamToVTK"], f, ifstarted=False) 91 | self.s = s 92 | 93 | class compileSlurm(bashCompiler): 94 | '''this is the slurm script for the case folder''' 95 | 96 | def __init__(self, folder:str, parentdir:str): 97 | super().__init__() 98 | # workdir = (os.path.join(parentdir, os.path.basename(folder))).replace("\\","/") 99 | workdir = parentdir.replace("\\","/") # RG 100 | s = f'#!/bin/bash\n#SBATCH -p local\n' 101 | s = s + '#SBATCH --time=42-00:00:00\n' 102 | s = s + '#SBATCH --nodes=1\n' 103 | s = s + '#SBATCH --cpus-per-task=1\n' 104 | s = s + f'#SBATCH --job-name={os.path.basename(folder)}\n' 105 | s = s + f'#SBATCH --chdir={workdir}\n' 106 | s = s + f'#SBATCH --partition=local\n\n' 107 | s = s + 'export OMP_NUM_THREADS=${SLURM_CPUS_PER_TASK}\n\n' 108 | s = s + 'srun bash mesh/Allrun.sh\n' 109 | s = s + 'srun bash case/Allrun.sh' 110 | self.s = s 111 | 112 | class compileAllRunMesh(bashCompiler): 113 | '''this script runs the meshing functions in the mesh folder''' 114 | 115 | def __init__(self, folder:str): 116 | super().__init__() 117 | s = self.binbash() 118 | s = s + 'cd \"$(dirname \"$0\")\" || exit; \n' 119 | s = s + '. $WM_PROJECT_DIR/bin/tools/RunFunctions;\n' 120 | s = s + 'if [ ! -d "VTK" ]; then\n' 121 | functionlist = ["surfaceFeatures", "blockMesh", "snappyHexMesh -overwrite", "foamToVTK"] 122 | # use surfaceFeatures for OpenFOAM 8 and later 123 | # use surfaceFeatureExtract otherwise 124 | s = self.fListLoop(s, functionlist, folder) 125 | s = s + 'fi \n' 126 | self.s = s -------------------------------------------------------------------------------- /py/initialize/compile_block_mesh_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import numpy as np 9 | import os,sys 10 | 11 | # local packages 12 | currentdir = os.path.dirname(os.path.realpath(__file__)) 13 | parentdir = os.path.dirname(currentdir) 14 | sys.path.append(parentdir) 15 | from block import Block 16 | from boundary_input import BoundaryInput 17 | from dict_list import DictList 18 | from initialize_tools import OpenFOAMFile, scale 19 | 20 | # logging 21 | logging.basicConfig(level=logging.INFO) 22 | 23 | #------------------------------------------------------------------------------------------------- 24 | 25 | 26 | class compileBlockMeshDict(OpenFOAMFile): 27 | '''get the blockMeshDict text 28 | pl is a point list 29 | blocks is a list of Block objects 30 | bl is a list of BoundaryInput objects''' 31 | 32 | def __init__(self, pl:np.array, blocks:List[Block], bl:List[BoundaryInput]) -> str: 33 | super().__init__() 34 | s = self.header("dictionary", "blockMeshDict") 35 | s = s + f"scale {scale()};\n\n" 36 | s = s + DictList("vertices", 1, pl[:, 0:3]).prnt(0) 37 | s = s + self.blocks2txt(blocks) 38 | s = s + "edges\n(\n);\n\n" 39 | s = s + self.boundarycpp(bl) 40 | s = s + "mergePatchPairs\n(\n);\n\n" 41 | s = s + self.closeLine() 42 | self.s = s 43 | 44 | def vec2cpp(self, v:List[float]) -> str: 45 | '''convert a vector to the format it needs to be in for OpenFOAM to read it 46 | v is a list''' 47 | 48 | s = "(" 49 | for vi in v: 50 | s = f'{s}{vi} ' 51 | s = s + ")" 52 | return s 53 | 54 | def block2txt(self, block:Block) -> str: 55 | '''convert block to openfoam string''' 56 | s = "hex " + self.vec2cpp(block.vertices[:, 3].astype(int)) + " " + self.vec2cpp(block.meshi) 57 | s = s + " simpleGrading " + self.vec2cpp(block.grading) 58 | return s 59 | 60 | def blocks2txt(self, blocks:List[Block]) -> str: 61 | '''convert list of blocks to openfoam string''' 62 | pl = DictList("blocks", 1, []) 63 | for b in blocks: 64 | pl.proplist.append(self.block2txt(b)) 65 | return pl.prnt(0) 66 | 67 | def faceSelector(self, block:Block, st:str) -> np.array: 68 | '''gets a list of vertices for a list 69 | block is a Block object 70 | st is a string indicating which face to use''' 71 | if st == "x-": 72 | li = [0, 4, 7, 3] 73 | elif st == "x+": 74 | li = [1, 2, 6, 5] 75 | elif st == "y-": 76 | li = [1, 5, 4, 0] 77 | elif st == "y+": 78 | li = [3, 7, 6, 2] 79 | elif st == "z-": 80 | li = [0, 3, 2, 1] 81 | elif st == "z+": 82 | li = [4, 5, 6, 7] 83 | return block.vertices[li, :] 84 | 85 | def boundaryList(self, blocks:List[Block]) -> List[BoundaryInput]: 86 | '''compiles a list of all the boundaries in the system for blockMesh 87 | because we are using snappyHexMesh, we only need one boundary in blockMesh''' 88 | allb = BoundaryInput("allBoundary", "patch") 89 | for st in ["x-", "x+", "y-", "y+", "z-", "z+"]: 90 | allb.flist.append(self.faceSelector(blocks[0], st)) # front and back faces 91 | return [allb] 92 | 93 | def boundarycpp(self, bl:List[BoundaryInput]) -> str: 94 | '''get the whole cpp section for list of boundaries''' 95 | bb = DictList("boundary", 1, []) 96 | for b in bl: 97 | # for each boundary in the boundary list 98 | thisboundary = DictList(b.label, 0, []) # label of the boundary 99 | thisboundary.proplist.append(["type patch"]) # boundary type 100 | thesefaces = DictList("faces", 1, []) # list of faces 101 | for f in b.flist: # extract list of faces, which are lists of points 102 | thesefaces.proplist.append(f[:,3].astype(int)) # for each list of points, take the last column 103 | thisboundary.proplist.append(thesefaces) # save faces to this boundary 104 | bb.proplist.append(thisboundary) # save this boundary to list of boundaries 105 | return bb.prnt(0) -------------------------------------------------------------------------------- /py/initialize/compile_control_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from cd_vars import CDVars 15 | from dict_list import DictList 16 | from initialize_tools import OpenFOAMFile 17 | 18 | # logging 19 | logging.basicConfig(level=logging.INFO) 20 | 21 | #------------------------------------------------------------------------------------------------- 22 | 23 | 24 | class compileControlDict(OpenFOAMFile): 25 | '''gets the text for controlDict''' 26 | 27 | def __init__(self, cdv:CDVars) -> str: 28 | super().__init__() 29 | s = self.header("dictionary", "controlDict") 30 | v = cdv.varList() 31 | #v.append(["#sinclude", "sampling"]) 32 | v.append(['#includeIfPresent', '\"sampling\"']) 33 | s = s + DictList("", 0,v).prnt(-1) 34 | s = s + self.closeLine() 35 | self.s = s -------------------------------------------------------------------------------- /py/initialize/compile_dynamic_mesh_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from mesh_vars import MeshVars 15 | from dict_list import DictList 16 | from initialize_tools import OpenFOAMFile 17 | 18 | # logging 19 | logging.basicConfig(level=logging.INFO) 20 | 21 | #------------------------------------------------------------------------------------------------- 22 | 23 | 24 | class compileDynamicMeshDict(OpenFOAMFile): 25 | 26 | def __init__(self, mv:MeshVars) -> str: 27 | super().__init__() 28 | s = self.header("dictionary", "dynamicMeshDict") 29 | simplelist = DictList("", 0, [["dynamicFvMesh", "dynamicRefineFvMesh"]]) # list to hold simple variables for dynamic meshing 30 | s = s + simplelist.prnt(-1) 31 | correctfluxes = DictList("correctFluxes", 1, \ 32 | [["phi", "none"], ["nHatf", "none"], ["rhoPhi", "none"],\ 33 | ["alphaPhi", "none"], ["ghf", "none"], ["flux(alpha.ink)", "none"],\ 34 | ["alphaPhi0.ink", "none"], ["alphaPhiUn", "none"], ["dVf_", "none"]]) 35 | dmd = mv.dmd() 36 | dmd.append(correctfluxes) 37 | # list to hold mesh coefficient dictionary entries for dynamic meshing 38 | s = s + DictList("dynamicRefineFvMeshCoeffs", 0, dmd).prnt(0) 39 | s = s + self.closeLine() 40 | self.s = s -------------------------------------------------------------------------------- /py/initialize/compile_fv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from fv_vars import FVVars 15 | from dict_list import DictList 16 | from initialize_tools import OpenFOAMFile 17 | from file_group import FileGroup 18 | 19 | # logging 20 | logging.basicConfig(level=logging.INFO) 21 | 22 | #------------------------------------------------------------------------------------------------- 23 | 24 | class compileFvSchemes(OpenFOAMFile): 25 | '''gets the text for the fvSchemes file. Input empty string to get file for mesh folder''' 26 | 27 | def __init__(self, fvv:Union[FVVars, str]): 28 | super().__init__() 29 | s = self.header("dictionary", "fvSchemes") 30 | if isinstance(fvv, FVVars): 31 | s = s + DictList("", 0, fvv.fvSchemeList()).prnt(-1) 32 | else: 33 | for st in ["gradSchemes", "divSchemes", "laplacianSchemes"]: 34 | s = s + st + "\n{\n}\n" 35 | s = s + self.closeLine() 36 | self.s = s 37 | 38 | class compileFvSolution(OpenFOAMFile): 39 | '''gets the text for fvSolution. Input empty string to get empty file for mesh folder''' 40 | 41 | def __init__(self, fvv:Union[FVVars, str]): 42 | super().__init__() 43 | s = self.header("dictionary", "fvSolution") 44 | if isinstance(fvv, FVVars): 45 | s = s + fvv.solverlist().prnt(0) 46 | s = s + fvv.pimple().prnt(0) 47 | s = s + DictList("relaxationFactors", 0, [DictList("equations", 0, [["\".*\" 1"]])]).prnt(0) 48 | s = s + self.closeLine() 49 | self.s = s -------------------------------------------------------------------------------- /py/initialize/compile_g.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from dict_list import DictList 15 | from initialize_tools import OpenFOAMFile 16 | 17 | # logging 18 | logging.basicConfig(level=logging.INFO) 19 | 20 | #------------------------------------------------------------------------------------------------- 21 | 22 | class compileG(OpenFOAMFile): 23 | '''compile g''' 24 | 25 | def __init__(self): 26 | super().__init__() 27 | s = self.header("uniformDimensionedVectorField", "g") 28 | s = s + DictList("", 0, [["dimensions", "[0 1 -2 0 0 0 0]"], ["value", "(0 0 -9.81)"]]).prnt(-1) 29 | s = s + self.closeLine() 30 | self.s = s -------------------------------------------------------------------------------- /py/initialize/compile_mesh_quality_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from initialize_tools import OpenFOAMFile 15 | 16 | # logging 17 | logging.basicConfig(level=logging.INFO) 18 | 19 | #------------------------------------------------------------------------------------------------- 20 | 21 | 22 | class compileMeshQualityDict(OpenFOAMFile): 23 | '''compile meshQualityDict''' 24 | 25 | def __init__(self): 26 | super().__init__() 27 | s = self.header("dictionary", "meshQualityDict") 28 | s = s + "#includeEtc \"caseDicts/mesh/generation/meshQualityDict\"\n\n" # RG 29 | s = s + self.closeLine() 30 | self.s = s -------------------------------------------------------------------------------- /py/initialize/compile_set_fields_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | import numpy as np 10 | 11 | # local packages 12 | currentdir = os.path.dirname(os.path.realpath(__file__)) 13 | parentdir = os.path.dirname(currentdir) 14 | sys.path.append(parentdir) 15 | from noz_vars import NozVars 16 | from dict_list import DictList 17 | from initialize_tools import OpenFOAMFile 18 | 19 | # logging 20 | logging.basicConfig(level=logging.INFO) 21 | 22 | #------------------------------------------------------------------------------------------------- 23 | 24 | 25 | class compileSetFieldsDict(OpenFOAMFile): 26 | 27 | 28 | def __init__(self, geo:NozVars): # fills the nozzle with ink at t=0 RG 29 | '''compile setFieldsDict''' 30 | s = self.header("dictionary", "setFieldsDict") 31 | s = s + DictList("defaultFieldValues", 1, ["volScalarFieldValue alpha.ink 0"]).prnt(0) 32 | r = DictList("regions", 1, []) 33 | # r is the region where the ink originally is 34 | 35 | if geo.na != 0: 36 | delta = 0.9*geo.nt/np.tan(np.deg2rad(geo.na)) # length of cylinders 37 | else: 38 | delta = 1 # avoids divide by 0 error 39 | 40 | steps = max(np.ceil((geo.bto-geo.nbo)/delta).astype('int'),1) # number of cylinders to create 41 | for x in range(steps): # creates thin enough cylinders of ink so that inside of the nozzle becomes fully ink but none of the bath becomes ink 42 | c2c = DictList("cylinderToCell", 0, []) 43 | # c2c is a cylinderToCell dictionary list 44 | ncx = geo.scale('ncx') 45 | ncy = geo.scale('ncy') 46 | d1 = round(geo.scale((geo.nbo+(geo.bto-geo.nbo)*x/steps)),8) 47 | d2 = round(geo.scale(geo.bto-(geo.bto-geo.nbo)*(steps-1-x)/steps),8) 48 | rtop = geo.scale(geo.niw/2+(x+1)*geo.nl/steps*np.tan(np.deg2rad(geo.na))) 49 | 50 | c2c.proplist.append([f'p1 ({ncx} {ncy} {d1})']) 51 | c2c.proplist.append([f'p2 ({ncx} {ncy} {d2})']) 52 | # top and bottom points of central cylinder axis 53 | c2c.proplist.append([f'radius {rtop}']) 54 | #radius of cylinder 55 | c2c.proplist.append(DictList("fieldValues", 1, ["volScalarFieldValue alpha.ink 1"])) 56 | # value of alpha inside the cylinder is 1 because it is full of ink 57 | r.proplist.append(c2c) 58 | 59 | s = s + r.prnt(0) 60 | s = s + self.closeLine() 61 | self.s = s -------------------------------------------------------------------------------- /py/initialize/compile_snappy_hex_mesh_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from initialize_tools import OpenFOAMFile 15 | from boundary_input import BoundaryInput 16 | from mesh_vars import MeshVars 17 | from dict_list import DictList 18 | 19 | # logging 20 | logging.basicConfig(level=logging.INFO) 21 | 22 | #------------------------------------------------------------------------------------------------- 23 | 24 | 25 | class compileSnappyHexMeshDict(OpenFOAMFile): 26 | '''compile SnappyHexMeshDict''' 27 | 28 | def __init__(self, bl:List[BoundaryInput], mv:MeshVars) -> str: 29 | super().__init__() 30 | bnames = [o.label for o in bl] 31 | s = self.header("dictionary", "snappyHexMeshDict") 32 | s = s + DictList("", 0, [\ 33 | ["castellatedMesh", mv.castellatedMesh],\ 34 | ["snap", mv.snap],\ 35 | ["addLayers", mv.addLayers]]).prnt(-1) 36 | s = s + DictList("geometry", 0,\ 37 | map(lambda x: DictList(x+".stl", 0, [["type", "triSurfaceMesh"], ["name ", x]]), bnames)).prnt(0) 38 | 39 | # castellated mesh controls 40 | cmc = mv.cmc() 41 | featuredictlist = DictList("features", 1, \ 42 | list(map(lambda b: DictList("", 0, [["file", f"\"{b.label}.eMesh\""], ["level", b.reflev]]), bl))) 43 | refinementsurfaces = DictList("refinementSurfaces", 0,\ 44 | list(map(lambda b: DictList(b.label, 0, [["level", f"({b.reflev} {b.reflev})"]]), bl))) 45 | cmc.append(featuredictlist) 46 | cmc.append(refinementsurfaces) 47 | cmc.append(DictList("refinementRegions", 0, [[]])) 48 | s = s + DictList("castellatedMeshControls", 0, cmc).prnt(0) 49 | 50 | s = s + DictList("snapControls", 0, mv.sc()).prnt(0) 51 | 52 | # add layers controls 53 | alc = mv.alc() 54 | layerslist = [] 55 | for o in bl: 56 | if o.reflev>0: 57 | layerslist.append(DictList(o.label, 0, [["nSurfaceLayers", mv.nSurfaceLayers]])) 58 | alc.append(DictList("layers", 0, layerslist)) 59 | s = s + DictList("addLayersControls", 0, alc).prnt(0) 60 | 61 | # mesh quality controls 62 | mqc = mv.mqc() 63 | mqc.append(["#include \"meshQualityDict\""]) 64 | mqc.append(DictList("relaxed", 0, [["maxNonOrtho", 65]])) 65 | s = s + DictList("meshQualityControls", 0, mqc).prnt(0) 66 | 67 | s = s + DictList("writeFlags", 1, ["scalarLevels", "layerSets", "layerFields"]).prnt(0) 68 | s = s + DictList("", 0, [["mergeTolerance", "1E-6"]]).prnt(-1) 69 | s = s + self.closeLine() 70 | self.s = s -------------------------------------------------------------------------------- /py/initialize/compile_surface_feature_extract_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from boundary_input import BoundaryInput 15 | from dict_list import DictList 16 | from initialize_tools import OpenFOAMFile 17 | 18 | # logging 19 | logging.basicConfig(level=logging.INFO) 20 | 21 | #------------------------------------------------------------------------------------------------- 22 | 23 | 24 | class compileSurfaceFeatureExtractDict(OpenFOAMFile): 25 | '''compile surfaceFeatureExtractDict''' 26 | 27 | def __init__(self, bl:List[BoundaryInput]) -> str: 28 | super().__init__() 29 | bnames = [o.label for o in bl] 30 | s = self.header("dictionary", "surfaceFeatureExtractDict") 31 | s = s + DictList("", 0, \ 32 | list(map(lambda x: DictList(x+".stl", 0, \ 33 | [["extractionMethod", "extractFromSurface"],\ 34 | DictList("extractFromSurfaceCoeffs", 0, [["includedAngle", 180]]),\ 35 | ["writeObj", "yes"]]), bnames))).prnt(-1) 36 | s = s + self.closeLine() 37 | self.s = s -------------------------------------------------------------------------------- /py/initialize/compile_surface_features_dict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from boundary_input import BoundaryInput 15 | from dict_list import DictList 16 | from initialize_tools import OpenFOAMFile 17 | 18 | # logging 19 | logging.basicConfig(level=logging.INFO) 20 | 21 | #------------------------------------------------------------------------------------------------- 22 | 23 | class compileSurfaceFeaturesDict(OpenFOAMFile): 24 | '''compile surfaceFeaturesDict''' 25 | 26 | def __init__(self, bl:List[BoundaryInput]): 27 | super().__init__() 28 | 29 | bnames = [o.label for o in bl] 30 | s = self.header("dictionary", "surfaceFeaturesDict") 31 | s = s + DictList("surfaces", 1, [f'"{b}.stl"' for b in bnames]).prnt(0) 32 | s = s + DictList("", 0, [["includedAngle", 180]]).prnt(-1) 33 | s = s + DictList("", 0, [["writeObj", "yes"]]).prnt(-1) 34 | s = s + self.closeLine() 35 | self.s = s -------------------------------------------------------------------------------- /py/initialize/compile_transport_properties.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from dict_list import DictList 15 | from initialize_tools import OpenFOAMFile 16 | 17 | # logging 18 | logging.basicConfig(level=logging.INFO) 19 | 20 | #------------------------------------------------------------------------------------------------- 21 | 22 | 23 | class compileTransportProperties(OpenFOAMFile): 24 | '''compile transportProperties 25 | ink and sup are DictLists created by transportGroupNewt and transportGroupHB 26 | sigma is the surface tension in J/m^2''' 27 | 28 | def __init__(self, ink:DictList, sup:DictList, sigma:Union[float, str]) -> str: 29 | super().__init__() 30 | s = self.header("dictionary", "transportProperties") 31 | s = s + DictList("", 0, [["phases (ink sup)"],ink, sup, ["sigma", str(sigma)]]).prnt(-1) 32 | s = s + self.closeLine() 33 | self.s = s -------------------------------------------------------------------------------- /py/initialize/compile_turbulence_properties.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from dict_list import DictList 15 | from initialize_tools import OpenFOAMFile 16 | 17 | # logging 18 | logging.basicConfig(level=logging.INFO) 19 | 20 | #------------------------------------------------------------------------------------------------- 21 | 22 | 23 | class compileTurbulenceProperties(OpenFOAMFile): 24 | 25 | def __init__(self): 26 | super().__init__() 27 | s = self.header("dictionary", "turbulenceProperties") 28 | s = s + DictList("", 0, [["simulationType", "laminar"]]).prnt(-1) 29 | s = s + self.closeLine() 30 | self.s = s 31 | -------------------------------------------------------------------------------- /py/initialize/creator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | # external packages 5 | import os 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | 9 | # local packages 10 | from file_creator import fileCreator 11 | from fluid import Fluid 12 | 13 | # logging 14 | logging.basicConfig(level=logging.INFO) 15 | 16 | #------------------------------------------------------------------------------------------------- 17 | 18 | def genericExport(ii:Union[int,str], sup:Fluid, ink:Fluid, sigma:float, topFolder:str, exportMesh:bool=False, **kwargs) -> None: 19 | ''' Export a folder, given a support fluid, ink fluid, and surface tension. 20 | ii is for the folder label. If you want it to be labeled nb#, input a number. Otherwise, input a string. 21 | sup is a fluid object that holds info about the support transport properties 22 | ink is a fluid object that holds info about the ink transport properties 23 | sigma is in J/m^2 (e.g. 0.04) 24 | topFolder is the folder to save this new folder in 25 | exportMesh true to export geometry folders into this folder''' 26 | out = fileCreator(ii, topFolder, exportMesh=exportMesh, **kwargs) 27 | out.addTransportProperties(ink, sup, sigma) 28 | out.export() 29 | 30 | def genericMesh(parentFolder:str, **kwargs) -> fileCreator: 31 | '''This generates a folder with mesh and geometry files, but nothing else important. If you want to customize the nozzle, input keyword variables. e.g. genericMesh(myFolder, bathWidth=16)''' 32 | out = fileCreator('temp', parentFolder, exportMesh=True, onlyMesh=True, **kwargs) # generate mesh files 33 | out.export() # export all of the mesh files 34 | out.plot() # make a plot of the boundaries 35 | return out 36 | -------------------------------------------------------------------------------- /py/initialize/creator_adjacent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | import pandas as pd 10 | 11 | # local packages 12 | currentdir = os.path.dirname(os.path.realpath(__file__)) 13 | parentdir = os.path.dirname(currentdir) 14 | sys.path.append(parentdir) 15 | sys.path.append(currentdir) 16 | from fluid import Fluid 17 | from tools.config import cfg 18 | from creator import genericExport, genericMesh 19 | from file.plainIm import plainIm 20 | 21 | # logging 22 | logging.basicConfig(level=logging.INFO) 23 | 24 | #------------------------------------------------------------------------------------------------- 25 | 26 | class adjacentFluid: 27 | '''a class for describing fluids for printing adjacent lines''' 28 | 29 | def __init__(self, model:str): 30 | if model=='HerschelBulkley': 31 | self.rho = '1000'; 32 | self.m = 'HerschelBulkley'; 33 | self.nu0 = '10' 34 | self.tau0 = '0.01' 35 | self.k = '0.00375' 36 | self.n = 0.45 37 | rho = float(self.rho) 38 | tau0 = float(self.tau0)*10**3 39 | k = float(self.k)*10**3 40 | n = float(self.n) 41 | nu0 = float(self.nu0)*10**3 42 | self.obj = Fluid(units = "Pa", tau0=tau0, k=k, n=n, nu0=nu0, rho=rho) 43 | elif model=='Newtonian': 44 | self.rho = '1000' 45 | self.m = 'Newtonian' 46 | self.nu = '0.001' 47 | rho = float(self.rho) 48 | nu = float(self.nu)*10**3 49 | self.obj = Fluid(units = "Pa", label = '', nu = nu , rho = rho) 50 | 51 | 52 | class adjacentCreator: 53 | '''for creating simulations for adjacent paper 54 | orientation must be in ['y', 'z'] 55 | vink should be in [0,10] 56 | sigma should be in ['0.04', '0'] 57 | inkModel and supportModel should be in ['HerschelBulkley', 'Newtonian'] 58 | dist is the spacing between filaments in nozzle inner diameters 59 | ii is the simulation number 60 | ''' 61 | 62 | def __init__(self, orientation:str, vink:float, sigma:str, dist:float, ii:int, inkModel:str='Newtonian', supModel:str='Newtonian'): 63 | self.fluids = {} 64 | self.fixSigma(sigma) 65 | self.orientation = orientation 66 | self.vink = vink 67 | self.ink = adjacentFluid(inkModel) 68 | self.sup = adjacentFluid(supModel) 69 | self.dist = dist 70 | self.iiname = f'aj{ii}' 71 | self.findModel() 72 | self.topfolder = os.path.join(cfg.path.c, 'adjacent') # this is the parent folder that holds everything 73 | 74 | def fixSigma(self, s): 75 | sigma = float(s) 76 | if sigma==0: 77 | self.sigma = '0' 78 | return 79 | if sigma>0.1: 80 | # wrong units 81 | sigma = sigma/1000 82 | self.sigma = str(sigma) 83 | 84 | def export(self): 85 | genericExport(self.iiname, self.sup.obj, self.ink.obj 86 | , self.sigma, self.topfolder, reffolder=self.rf 87 | , adjacent=self.orientation, distance=self.dist, vink=self.vink 88 | , exportMesh=True, slurmFolder=f'/working/lmf1/adjacent/{self.iiname}') 89 | 90 | def exportMesh(self, dist:float=0.875, adj:str='y'): 91 | fc = genericMesh(cfg.path.c, reffolder=self.rf, adjacent=self.orientation 92 | , distance=self.dist, vink=self.vink) # generate mesh files 93 | fn = os.path.join(cfg.path.fig, 'adjacent', 'nozzleDiagramAdj.svg') 94 | fc.fg.plot.savefig(fn) # export the diagram of the nozzle 95 | logging.info(f'Exported {fn}') 96 | 97 | 98 | def extractSim(self, **kwargs) -> str: # RG 99 | '''Extract simulation number from the general legend based on ink and support rheology 100 | outputs a simulation string''' 101 | file = os.path.join(cfg.path.server, 'viscositysweep\legend_general.csv') 102 | d,u = plainIm(file, ic=0) 103 | d = d[(d['ink_transportmodel']==self.ink.m) & (d['sup_transportmodel']==self.sup.m)] 104 | d = d[(d['ink_rho']==float(self.ink.rho)) & (d['sup_rho']==float(self.sup.rho)) & (d['sigma']==float(self.sigma))] 105 | 106 | if self.ink.m=='Newtonian': 107 | d = d[(d['ink_nu']==float(self.ink.nu))] 108 | elif self.ink.m=='HerschelBulkley': 109 | d = d[(d['ink_tau0']==float(self.ink.tau0)) & (d['ink_k']==float(self.ink.k)) & (d['ink_n']==float(self.ink.n))] 110 | else: 111 | raise ValueError(f'Unexpected ink model {self.ink.m}') 112 | if self.sup.m=='Newtonian': 113 | d = d[(d['sup_nu']==float(self.sup.nu))] 114 | elif self.sup.m=='HerschelBulkley': 115 | d = d[(d['sup_tau0']==float(self.sup.tau0)) & (d['sup_k']==float(self.sup.k)) & (d['sup_n']==float(self.sup.n))] 116 | else: 117 | raise ValueError(f'Unexpected sup model {self.sup.m}') 118 | 119 | if d.empty: 120 | raise Exception('No simulations match that rheology') 121 | sim = d['folder'].values[0] 122 | return sim 123 | 124 | def findModel(self): 125 | '''find the reference simulation path''' 126 | ref = self.extractSim() 127 | if self.ink.m=='Newtonian': 128 | if self.sup.m=='Newtonian': 129 | folder = 'newtnewtsweep' 130 | elif self.sup.m=='HerschelBulkley': 131 | folder = 'HBnewtsweep' 132 | elif self.ink.m=='HerschelBulkley': 133 | if self.sup.m=='Newtonian': 134 | folder = 'newtHBsweep' 135 | elif self.sup.m=='HerschelBulkley': 136 | folder = 'HBHBsweep' 137 | else: 138 | raise ValueError(f'Unexpected support model {self.sup.m}') 139 | else: 140 | raise ValueError(f'Unexpected ink model {self.ink.m}') 141 | 142 | self.rf = os.path.join(cfg.path.server, 'viscositysweep', folder, ref) 143 | -------------------------------------------------------------------------------- /py/initialize/dict_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | 9 | # local packages 10 | 11 | 12 | # logging 13 | logging.basicConfig(level=logging.INFO) 14 | 15 | 16 | 17 | #------------------------------------------------------------------------------------------------- 18 | 19 | 20 | class DictList: 21 | '''DictList is an object that ensures uniform formatting of variables into files''' 22 | 23 | def __init__(self, title:str, form:int, proplist:List[str]): 24 | '''Inputs: title, form, proplist 25 | title is the title of the variable group 26 | form is 0 for bracketed entries e.g. group definitions, 1 for parentheses e.g. point lists 27 | proplist is a list of properties that we're storing within this group''' 28 | self.title = title 29 | self.proplist = proplist 30 | if form==0: 31 | self.pl = "{" # parens (group) left 32 | self.pr = "}" # parens (group) right 33 | self.sp = "\t" # spacing 34 | self.rl = "" # row left 35 | self.rr = ";" # row right 36 | else: 37 | self.pl = "(" 38 | self.pr = ");" 39 | self.sp = " " 40 | self.rl = "(" 41 | self.rr = ")" 42 | 43 | def tabs(self, n:int) -> str: 44 | '''return n tabs in a row''' 45 | s = "" 46 | for i in range(n): 47 | s = s + "\t" 48 | return s 49 | 50 | 51 | def prnt(self, level:int) -> str: 52 | '''Format the title and variables into the format that OpenFOAM likes. 53 | Input: the number of tabs before the title of the group. If you want no title, use level -1 54 | Output: formatted string''' 55 | if level<0: 56 | s = "" 57 | else: 58 | s = f'{self.tabs(level)}{self.title}\n{self.tabs(level)}{self.pl}\n' 59 | for p in self.proplist: 60 | if isinstance(p, DictList): # if this property is a DictList, recurse 61 | s = s + p.prnt(level + 1) 62 | else: # row level 63 | s = s + self.tabs(level + 1) 64 | if isinstance(p, str): # single item 65 | s = f'{s}{p}\n' 66 | else: # list of items 67 | if len(p)>0: 68 | # one row 69 | s = s + self.rl 70 | for pi in p[0:-1]: 71 | s = f'{s}{pi}{self.sp}' 72 | s = f'{s}{p[-1]}{self.rr}\n' 73 | else: 74 | s = f'{s}\n' 75 | if level<0: 76 | s = f'{s}\n' 77 | if level>=0: 78 | s = f'{s}{self.tabs(level)}{self.pr}\n\n' 79 | return s -------------------------------------------------------------------------------- /py/initialize/export.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | from stl import mesh 9 | import numpy as np 10 | import os,sys 11 | 12 | # local packages 13 | currentdir = os.path.dirname(os.path.realpath(__file__)) 14 | parentdir = os.path.dirname(currentdir) 15 | sys.path.append(parentdir) 16 | from boundary_input import BoundaryInput 17 | from initialize_tools import scale 18 | 19 | # logging 20 | logging.basicConfig(level=logging.INFO) 21 | 22 | #------------------------------------------------------------------------------------------------- 23 | 24 | def mkdirif(path:str) -> int: 25 | '''makes a directory if it doesn't already exist 26 | path is the directory to create''' 27 | try: 28 | os.mkdir(path) 29 | except OSError: 30 | return 0 31 | else: 32 | logging.info("Created directory %s" % path) 33 | 34 | def saveStls(folder:str, bl:List[BoundaryInput]) -> None: 35 | '''exports stls 36 | folder is a full path name 37 | bl is a list of boundaryInput objects ''' 38 | for b in bl: 39 | me = b.meshi 40 | me2 = mesh.Mesh(np.zeros(len(me), dtype=mesh.Mesh.dtype)) 41 | for i,m in enumerate(me['vectors']): 42 | me2.vectors[i] = m*scale() 43 | title = os.path.join(folder, f'{b.label}.stl') 44 | me2.save(title) 45 | 46 | def replaceCR(file:str) -> None: 47 | '''replace windows carriage return with linux carriage return. Otherwise bash scripts will throw error. From https://stackoverflow.com/questions/36422107/how-to-convert-crlf-to-lf-on-a-windows-machine-in-python''' 48 | WINDOWS_LINE_ENDING = b'\r\n' 49 | UNIX_LINE_ENDING = b'\n' 50 | 51 | with open(file, 'rb') as open_file: 52 | content = open_file.read() 53 | 54 | content = content.replace(WINDOWS_LINE_ENDING, UNIX_LINE_ENDING) 55 | 56 | with open(file, 'wb') as open_file: 57 | open_file.write(content) 58 | 59 | def exportFile(folder:str, file:str, text:str, linux:bool=False) -> None: 60 | '''exports text files 61 | folder is a full path name 62 | file is a basename 63 | text is the text to export''' 64 | fn = os.path.join(folder, file) 65 | File_object = open(fn,"w") 66 | File_object.write(text) 67 | File_object.close() 68 | logging.debug("Exported %s" % fn) 69 | if linux: 70 | replaceCR(fn) 71 | 72 | -------------------------------------------------------------------------------- /py/initialize/file_group.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import os,sys 8 | import logging 9 | import numpy as np 10 | 11 | # local packages 12 | currentdir = os.path.dirname(os.path.realpath(__file__)) 13 | parentdir = os.path.dirname(currentdir) 14 | sys.path.append(parentdir) 15 | from tools.config import cfg 16 | from block import Block 17 | from boundary_input import BoundaryInput 18 | import export as ex 19 | import folder_scraper as fs 20 | from noz_vars import NozVars 21 | from file_plotter import filePlotter 22 | 23 | 24 | # logging 25 | logging.basicConfig(level=logging.INFO) 26 | 27 | 28 | 29 | #------------------------------------------------------------------------------------------------- 30 | 31 | class FileGroup: 32 | '''This holds all of the strings that get outputted to text files and the meshes used to generate stls''' 33 | 34 | def __init__(self, folder:str, exportMesh:bool=False, onlyMesh:bool=False, **kwargs): 35 | '''Input is the folder that all of these files will go into''' 36 | 37 | self.exportMesh = exportMesh 38 | self.onlyMesh = onlyMesh 39 | self.folder = folder 40 | self.geofile = "" 41 | self.allclean = "" # for both mesh and case folders 42 | self.allallrun = "" # for the folder above mesh and case 43 | self.allrun = "" 44 | self.allrunmesh = "" 45 | self.cont = "" 46 | 47 | self.alphainkorig = "" 48 | self.prgh = "" 49 | self.U = "" 50 | 51 | self.g = "" 52 | self.transportProperties = "" 53 | self.turbulenceProperties = "" 54 | self.dynamicMeshDict = "" 55 | 56 | self.blockMeshDict = "" 57 | self.controlDict = "" 58 | self.controlDictMesh = "" 59 | self.fvSchemes = "" 60 | self.fvSchemesMesh = "" 61 | self.fvSolution = "" 62 | self.fvSolutionMesh = "" 63 | self.setFieldsDict = "" 64 | 65 | self.cellLevel = "" 66 | self.pointLevel = "" 67 | self.meshQualityDict = "" 68 | self.snappyHexMeshDict = "" 69 | self.surfaceFeatureExtractDict = "" 70 | self.surfaceFeaturesDict = "" 71 | 72 | if 'slurmFolder' in kwargs: 73 | self.slurmFolder = kwargs['slurmFolder'] 74 | else: 75 | self.slurmFolder = os.path.join(cfg.path.slurmFolder, os.path.basename(folder)) 76 | 77 | self.plot = "" 78 | 79 | self.meshes = [] 80 | 81 | def exportAllFiles(self) -> str: 82 | '''exports all of the files that we generated. exportMesh is true to export mesh files in this folder''' 83 | f = self.folder 84 | folderList = [f] 85 | 86 | if not self.onlyMesh: 87 | casef = os.path.join(f, "case") 88 | f0 = os.path.join(casef, "0") 89 | fconst = os.path.join(casef,"constant") 90 | fsyst = os.path.join(casef,"system") 91 | folderList = folderList + [casef, f0, fconst, fsyst] 92 | 93 | if self.exportMesh: 94 | fgeom = os.path.join(f,"geometry") 95 | fmesh = os.path.join(f,"mesh") 96 | fmeshconst = os.path.join(fmesh,"constant") 97 | fmeshconsttri = os.path.join(fmeshconst,"triSurface") 98 | fmeshsys = os.path.join(fmesh,"system") 99 | fmesh0 = os.path.join(fmesh,"0") 100 | folderList = folderList + [fgeom, fmesh, fmeshconst, fmeshconsttri, fmeshsys, fmesh0] 101 | 102 | list(map(ex.mkdirif, folderList)) # create folders 103 | 104 | 105 | if not self.onlyMesh: 106 | # ex.exportFile(f, 'labels.csv', self.labels) 107 | ex.exportFile(f, 'run.slurm', self.slurm, linux=True) 108 | ex.exportFile(f, "Allclean.sh", self.allallclean, linux=True) 109 | ex.exportFile(casef, "Allclean.sh", self.allclean, linux=True) 110 | # ex.exportFile(casef, "Allrun", self.allrun) 111 | ex.exportFile(casef, 'Allrun.sh', self.allrun, linux=True) 112 | # ex.exportFile(casef, "Continue", self.cont) 113 | ex.exportFile(f0, "alpha.ink.orig", self.alphainkorig) 114 | ex.exportFile(f0, "alpha.ink", self.alphainkorig) 115 | ex.exportFile(f0, "p_rgh", self.prgh) 116 | ex.exportFile(f0, "U", self.U) 117 | ex.exportFile(fconst, "g", self.g) 118 | ex.exportFile(fconst, "transportProperties", self.transportProperties) 119 | ex.exportFile(fconst, "turbulenceProperties", self.turbulenceProperties) 120 | ex.exportFile(fconst, "dynamicMeshDict", self.dynamicMeshDict) 121 | ex.exportFile(fsyst, "controlDict", self.controlDict) 122 | ex.exportFile(fsyst, "fvSchemes", self.fvSchemes) 123 | ex.exportFile(fsyst, "fvSolution", self.fvSolution) 124 | ex.exportFile(fsyst, "setFieldsDict", self.setFieldsDict) 125 | 126 | if self.exportMesh: 127 | if not self.onlyMesh: 128 | ex.exportFile(f, "Allrun.sh", self.allallrun, linux=True) 129 | ex.exportFile(f, "geometry.csv", self.geofile) 130 | # ex.exportFile(fmesh, "Allclean.sh", self.allclean, linux=True) 131 | ex.exportFile(fmesh, "Allrun.sh", self.allrunmesh, linux=True) 132 | ex.exportFile(fmesh0, "pointLevel", self.pointLevel) 133 | ex.exportFile(fmesh0, "cellLevel", self.cellLevel) 134 | ex.exportFile(fmeshsys, "blockMeshDict", self.blockMeshDict) 135 | ex.exportFile(fmeshsys, "controlDict", self.controlDictMesh) 136 | ex.exportFile(fmeshsys, "fvSchemes", self.fvSchemesMesh) 137 | ex.exportFile(fmeshsys, "fvSolution", self.fvSolutionMesh) 138 | ex.exportFile(fmeshsys, "meshQualityDict", self.meshQualityDict) 139 | ex.exportFile(fmeshsys, "snappyHexMeshDict", self.snappyHexMeshDict) 140 | ex.exportFile(fmeshsys, "surfaceFeatureExtractDict", self.surfaceFeatureExtractDict) 141 | ex.exportFile(fmeshsys, "surfaceFeaturesDict", self.surfaceFeaturesDict) 142 | ex.saveStls(fgeom, self.meshes) 143 | ex.saveStls(fmeshconsttri, self.meshes) 144 | 145 | if not self.onlyMesh: 146 | fs.populate(self.folder) 147 | 148 | 149 | def makePlot(self, elev:int=20, azim:int=145, showMesh:bool=False 150 | , vertexNumbers:bool=False, blockNumbers:bool=False 151 | , fs:int=10, figsize:float=10, **kwargs): 152 | '''plot the requested geometry 153 | geo is a nozzleVars object 154 | pl is a point list used for blockMesh 155 | blocks is a list of blocks used for blockMesh 156 | br is real boundaries used for generating stls and setting boundary conditions''' 157 | self.plotter = filePlotter(self.geo, self.pl, self.blocks, self.br) 158 | self.plotter.makePlot(**kwargs) 159 | self.plot = self.plotter.fig 160 | -------------------------------------------------------------------------------- /py/initialize/file_plotter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import os,sys 8 | import logging 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | from mpl_toolkits.mplot3d import Axes3D 12 | from mpl_toolkits.mplot3d.art3d import Poly3DCollection 13 | from matplotlib import cm 14 | import matplotlib.patches as mpatches 15 | 16 | # local packages 17 | currentdir = os.path.dirname(os.path.realpath(__file__)) 18 | parentdir = os.path.dirname(currentdir) 19 | sys.path.append(parentdir) 20 | from block import Block 21 | from boundary_input import BoundaryInput 22 | from noz_vars import NozVars 23 | 24 | 25 | # logging 26 | logging.basicConfig(level=logging.INFO) 27 | 28 | 29 | 30 | #------------------------------------------------------------------------------------------------- 31 | 32 | 33 | class filePlotter: 34 | '''for plotting the requested geometry''' 35 | 36 | def __init__(self, geo, pl, blocks, br, elev:int=10, azim:int=190, fs:int=10, **kwargs): 37 | self.geo = geo 38 | self.pl = pl 39 | self.blocks = blocks 40 | self.br = br 41 | self.elev = elev 42 | self.azim = azim 43 | self.fs = fs 44 | self.makePlot(**kwargs) 45 | 46 | def makePlot(self, showMesh:bool=False 47 | , vertexNumbers:bool=False, blockNumbers:bool=False 48 | , figsize:float=10, **kwargs): 49 | '''plot the requested geometry 50 | geo is a nozzleVars object 51 | pl is a point list used for blockMesh 52 | blocks is a list of blocks used for blockMesh 53 | br is real boundaries used for generating stls and setting boundary conditions''' 54 | self.fig = plt.figure(figsize=[figsize,figsize*1.5]) 55 | plt.rc('font',family='Arial') 56 | self.ax = self.fig.add_subplot(111, projection='3d') 57 | self.ax.view_init(elev=self.elev, azim=self.azim) 58 | self.ax.set_clip_on(False) 59 | maxdim = max([self.geo.bw, self.geo.bh, self.geo.bd]) 60 | self.ax.set_xlim3d(self.geo.ble, self.geo.ble+maxdim) 61 | self.ax.set_ylim3d(self.geo.bfr, self.geo.bfr+maxdim) 62 | self.ax.set_zlim3d(self.geo.bbo, self.geo.bbo+maxdim) 63 | self.labelX() 64 | self.labelY() 65 | self.labelZ() 66 | self.ax.scatter(self.pl[:,0], self.pl[:,1], self.pl[:,2], s=self.fs, c='k') 67 | if blockNumbers: 68 | for p in self.pl: 69 | self.ax.text(p[0], p[1], p[2], "{:.0f}".format(p[3]), color='k', fontsize=self.fs) 70 | if vertexNumbers: 71 | for i, block in enumerate(self.blocks): 72 | vs = block.vertices 73 | self.ax.text(np.mean(vs[:,0]), np.mean(vs[:,1]), np.mean(vs[:,2]), i, color='r', fontsize=self.fs) 74 | if len(self.br)==4: 75 | clist = ['tomato', 'navajowhite', 'mediumaquamarine', 'deepskyblue', 'white'] 76 | else: 77 | clist = ['tomato', 'navajowhite', '#d1b721', 'gray', 'mediumaquamarine', 'deepskyblue'] 78 | plist = [] 79 | for i, bi in enumerate(self.br): 80 | # each boundary bi in list bl 81 | col = clist.pop(0) # pop a color from the front of the list 82 | plist.append(mpatches.Patch(color=col, label=bi.label)) 83 | for m in bi.meshi['vectors']: 84 | p3c = Poly3DCollection([list(zip(m[:,0],m[:,1],m[:,2]))], alpha=0.35) 85 | p3c.set_facecolor(col) 86 | if showMesh: 87 | p3c.set_edgecolor('gray') 88 | else: 89 | p3c.set_edgecolor(None) 90 | self.ax.add_collection3d(p3c) 91 | self.ax.text(self.geo.ble+maxdim, self.geo.bfr+maxdim, self.geo.bbo+i/len(self.br)*self.geo.bd, bi.label, color=col, fontfamily='Arial', fontsize=self.fs) # legend 92 | self.ax.grid(False) 93 | self.ax.axis(False) 94 | self.ax.set_facecolor('White') 95 | self.ax.set_xticks([]) 96 | self.ax.set_yticks([]) 97 | self.ax.set_zticks([]) 98 | self.ax.get_proj = lambda: np.dot(Axes3D.get_proj(self.ax), np.diag([1,1,1.3, 1])) 99 | 100 | 101 | def labelX(self): 102 | x = [self.geo.ble, self.geo.ble+self.geo.bw] 103 | if self.elev<0: 104 | # label top 105 | z = self.geo.bbo+self.geo.bd 106 | va = 'bottom' 107 | else: 108 | z = self.geo.bbo 109 | va = 'top' 110 | if self.azim>0 and self.azim<180: 111 | # label back 112 | y = self.geo.bfr+self.geo.bh + 0.5 113 | ha = 'right' 114 | else: 115 | y = self.geo.bfr - 0.5 116 | ha = 'left' 117 | self.ax.quiver(x[0], y, z, self.geo.bw, 0,0, color='Black', arrow_length_ratio=0.1) 118 | self.ax.text(np.mean(x), y, z, f'{self.geo.bw} mm', color='Black', fontfamily='Arial', fontsize=self.fs, verticalalignment=va, horizontalalignment=ha) 119 | self.ax.text(x[1], y, z, 'x', color='Black', fontfamily='Arial', fontsize=self.fs, verticalalignment=va, horizontalalignment=ha) 120 | 121 | def labelY(self): 122 | y = [self.geo.bfr, self.geo.bfr+self.geo.bh] 123 | if self.elev<0: 124 | # label top 125 | z = self.geo.bbo+self.geo.bd 126 | va = 'bottom' 127 | else: 128 | z = self.geo.bbo 129 | va = 'top' 130 | if self.azim>90 and self.azim<270: 131 | # label back 132 | x = self.geo.ble - 0.5 133 | ha = 'left' 134 | else: 135 | x = self.geo.ble+self.geo.bw + 0.5 136 | ha = 'right' 137 | self.ax.quiver(x, y[0], z, 0, self.geo.bh, 0, color='Black', arrow_length_ratio=0.1) 138 | self.ax.text(x, np.mean(y), z, f'{self.geo.bh} mm', color='Black', fontfamily='Arial', fontsize=self.fs, verticalalignment=va, horizontalalignment=ha) 139 | self.ax.text(x, y[1], z, 'y', color='Black', fontfamily='Arial', fontsize=self.fs, verticalalignment=va, horizontalalignment=ha) 140 | 141 | def labelZ(self): 142 | '''put a label on the z axis''' 143 | z = [self.geo.bbo, self.geo.bbo+self.geo.bd] 144 | x0 = self.geo.ble - 0.5 145 | xf = self.geo.ble+self.geo.bw + 0.5 146 | y0 = self.geo.bfr - 0.5 147 | yf = self.geo.bfr+self.geo.bh + 0.5 148 | ha = 'right' 149 | if self.azim>=0 and self.azim<90: 150 | y = y0 151 | x = xf 152 | elif self.azim>=90 and self.azim<180: 153 | y = yf 154 | x = xf 155 | elif self.azim>=180 and self.azim<270: 156 | y = yf 157 | x = x0 158 | else: 159 | y = y0 160 | x = x0 161 | self.ax.quiver(x, y, z[0], 0, 0, self.geo.bd, color='Black', arrow_length_ratio=0.1) 162 | self.ax.text(x, y, np.mean(z), f'{self.geo.bd} mm', color='Black', fontfamily='Arial', fontsize=self.fs, horizontalalignment=ha) 163 | self.ax.text(x, y, z[1], 'z', color='Black', fontfamily='Arial', fontsize=self.fs, horizontalalignment=ha) -------------------------------------------------------------------------------- /py/initialize/fluid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from transport_group import transportGroupNewt, transportGroupHB 15 | 16 | # logging 17 | logging.basicConfig(level=logging.INFO) 18 | 19 | #------------------------------------------------------------------------------------------------- 20 | 21 | class Fluid: 22 | '''OpenFOAM needs to use kinematic units (see: kinematic vs. dynamic viscosity). If viscosities are given in dynamic units (e.g. Pa*s for viscosity, Pa for stress), then they need to be normalized by the density. We indicate whether units are kinematic or dynamic using 'units'. Units that mention Pa or dynamic will be considered dynamic. If no units are given, kinematic units are assumed.''' 23 | def __init__(self, units:str='kinematic', **kwargs): 24 | if 'rho' in kwargs: 25 | self.rho = kwargs['rho'] 26 | if self.rho<500: 27 | logging.warning('Density is very low. Density should be given in kg/m^3') 28 | else: 29 | self.rho = 1000 30 | 31 | if units in ['Pa', 'dynamic', 'Pa*s', 'Pas']: 32 | div = self.rho 33 | else: 34 | div = 1 35 | 36 | if 'tau0' in kwargs and 'n' in kwargs and 'k' in kwargs and 'nu0' in kwargs: 37 | self.model='HerschelBulkley' 38 | self.tau0 = kwargs['tau0']/div 39 | self.n = kwargs['n'] 40 | self.k = kwargs['k']/div 41 | self.nu0 = kwargs['nu0']/div 42 | elif 'nu' in kwargs: 43 | self.model='Newtonian' 44 | self.nu=kwargs['nu']/div 45 | else: 46 | raise ValueError('Invalid inputs. Required input for Newtonian is (nu). Required inputs for Herschel-Bulkley are (tau0, n, k, nu0).') 47 | 48 | if 'label' in kwargs: 49 | self.label = kwargs['label'] 50 | else: 51 | self.label = '' 52 | 53 | def transportGroup(self, name:str): 54 | if self.model=='Newtonian': 55 | return transportGroupNewt(name, self.nu, self.rho) 56 | elif self.model=='HerschelBulkley': 57 | return transportGroupHB(name, self.nu0, self.tau0, self.k, self.n, self.rho) 58 | 59 | 60 | -------------------------------------------------------------------------------- /py/initialize/fv_sol_grp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from dict_list import DictList 15 | 16 | # logging 17 | logging.basicConfig(level=logging.INFO) 18 | 19 | #------------------------------------------------------------------------------------------------- 20 | 21 | 22 | class FVSolGrp: 23 | '''fvsolution variables for a given solve variable (e.g. interFoam)''' 24 | 25 | def __init__(self, st:str, solver:str): 26 | '''Inputs: st, solver 27 | st is a string that tells us what variable is being solved for, e.g. 'alphaink', 'pcorr', 'prgh', 'prghfinal', or 'U' 28 | solver is the type of solver being used: 'interFoam' or 'interIsoFoam'. ''' 29 | 30 | self.badcharlist = [] 31 | 32 | if st=="alphaink": 33 | self.dicttitle = "\"alpha.ink.*\"" 34 | self.nAlphaSubCycles = 1 35 | self.cAlpha = 1 36 | if solver=="interIsoFoam": 37 | # these variables are specific to isoAdvector 38 | self.isofaceTol = "1e-6" # Error tolerance on alpha when cutting surface cells into sub-cells 39 | self.surfCellTol = "1e-6" # Only cells with surfCellTol < alpha < 1-surfCellTol 40 | # are treated as surface cells 41 | self.nAlphaBounds = 3 # Number of times the ad-hoc bounding step should 42 | # try to correct unboundedness. Strictly volume 43 | # conserving (provided that sum(phi) = 0 for a cell). 44 | self.snapTol = "1e-12" # Optional: cells with alpha < snapAlphaTol are 45 | # snapped to 0 and cells with 1 - alpha < 46 | # snapAlphaTol are snapped to 1 47 | self.clip = "true" # Optional: clip remaining unboundedness 48 | elif solver=="interFoam": 49 | # these variables are specific to MULES 50 | self.nAlphaCorr = 2 51 | self.MULESCorr = "yes" 52 | self.nLimiterIter = 5 53 | self.solver = "smoothSolver" 54 | self.smoother = "symGaussSeidel" 55 | self.tolerance = "1e-8" 56 | self.relTol = 0 57 | elif st=="pcorr": 58 | self.dicttitle = "\"pcorr.*\"" 59 | self.solver = "PCG" 60 | self.preconditioner = "DIC" 61 | self.tolerance = "1e-5" 62 | self.relTol = 0 63 | elif st=="prgh": 64 | self.dicttitle = "p_rgh" 65 | self.solver = "PCG" 66 | self.preconditioner = "DIC" 67 | if solver=="interIsoFoam": 68 | self.tolerance = "1e-8" 69 | else: 70 | self.tolerance = "1e-7" 71 | self.relTol = 0.05 72 | elif st == "prghfinal": 73 | self.dicttitle = "p_rghFinal" 74 | self.relTol = 0 75 | self.badcharlist = [["$p_rgh"]] 76 | elif st == "U": 77 | self.dicttitle = "U" 78 | self.solver = "smoothSolver" 79 | self.smoother = "symGaussSeidel" 80 | self.tolerance = "1e-6" 81 | self.relTol = 0 82 | 83 | def dl(self) -> DictList: 84 | '''Gives a DictList object which is used for printing variables to file''' 85 | l = self.badcharlist 86 | for attr, value in self.__dict__.items(): 87 | if attr!="dicttitle" and attr!="badcharlist": 88 | l.append([attr, value]) 89 | return DictList(self.dicttitle, 0, l) -------------------------------------------------------------------------------- /py/initialize/fv_vars.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from fv_sol_grp import FVSolGrp 15 | from dict_list import DictList 16 | 17 | # logging 18 | logging.basicConfig(level=logging.INFO) 19 | 20 | #------------------------------------------------------------------------------------------------- 21 | 22 | 23 | class FVVars: 24 | '''Holds all fvsolution and fvschemes variables''' 25 | 26 | 27 | def __init__(self, solver:str) -> None: 28 | '''Input: solver is the type of solver being used: 'interFoam' or 'interIsoFoam' ''' 29 | self.solver = solver 30 | 31 | # FVSchemes 32 | self.ddtSchemesDefault = "Euler" 33 | self.gradSchemesDefault = "Gauss linear" 34 | self.laplacianSchemesDefault = "Gauss linear corrected" 35 | self.interpolationSchemesDefault = "linear" 36 | self.snGradSchemesDefault = "corrected" 37 | self.divSchemes = [["div(rhoPhi,U) Gauss linearUpwind grad(U)"], ["div(phi,alpha) Gauss vanLeer"], \ 38 | ["div(phirb,alpha) Gauss linear"], ["div(((rho*nuEff)*dev2(T(grad(U))))) Gauss linear"]] 39 | self.slist = ["ddtSchemes", "gradSchemes", "laplacianSchemes", "interpolationSchemes", "snGradSchemes"] 40 | self.postlist = ["divSchemes"] 41 | if solver=="interIsoFoam": 42 | self.postlist.append("fluxRequired") 43 | self.fluxRequired = [["default", "no"], ["p_rgh"], ["pcorr"], ["alpha.ink"]] 44 | 45 | # FVSolution 46 | self.alphaink = FVSolGrp("alphaink", solver) 47 | self.pcorr = FVSolGrp("pcorr", solver) 48 | self.prgh = FVSolGrp("prgh", solver) 49 | self.prghfinal = FVSolGrp("prghfinal", solver) 50 | self.U = FVSolGrp("U", solver) 51 | 52 | # PIMPLE 53 | self.momentumPredictor = "no" 54 | self.nOuterCorrectors = 1 55 | self.nCorrectors = 3 56 | self.nNonOrthogonalCorrectors = 0 57 | 58 | 59 | def fvSchemeList(self) -> List[DictList]: 60 | '''format the fvSchemes variables into a list of DictLists so they are ready to print to file''' 61 | l = [] 62 | for v in self.slist: 63 | l.append(DictList(v, 0, [["default", getattr(self, v+"Default")]])) 64 | for v in self.postlist: 65 | l.append(DictList(v, 0, getattr(self, v))) 66 | return l 67 | 68 | 69 | def solverlist(self) -> DictList: 70 | '''for fvsolutions, get a list of DictLists created by the FVSolGrp objects so they are ready to print to file''' 71 | l = [] 72 | for o in [self.alphaink, self.pcorr, self.prgh, self.prghfinal, self.U]: 73 | l.append(o.dl()) 74 | return DictList("solvers", 0, l) 75 | 76 | 77 | def pimple(self) -> DictList: 78 | '''get a DictList for the PIMPLE variables''' 79 | l = [] 80 | for o in ["momentumPredictor", "nOuterCorrectors", "nCorrectors", "nNonOrthogonalCorrectors"]: 81 | l.append([o, getattr(self, o)]) 82 | return DictList("PIMPLE", 0, l) -------------------------------------------------------------------------------- /py/initialize/geometry_file.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from noz_vars import NozVars 15 | 16 | # logging 17 | logging.basicConfig(level=logging.INFO) 18 | 19 | #------------------------------------------------------------------------------------------------- 20 | 21 | class geometryFile: 22 | '''geometryFile gets a csv string of all of the geometry variables we care about''' 23 | 24 | def __init__(self, geo:NozVars): 25 | 26 | l = [['nozzle_inner_width', geo.niw, 'mm'],\ 27 | ['nozzle_thickness', geo.nt, 'mm'], \ 28 | ['bath_width', geo.bw, 'mm'], \ 29 | ['bath_height', geo.bh, 'mm'], \ 30 | ['bath_depth', geo.bd, 'mm'], \ 31 | ['nozzle_length', geo.nl, 'mm'],\ 32 | ['bath_left_coord', geo.ble, 'mm'], \ 33 | ['bath_right_coord', geo.bri, 'mm'],\ 34 | ['bath_front_coord', geo.bfr, 'mm'], \ 35 | ['bath_back_coord', geo.bba, 'mm'],\ 36 | ['bath_bottom_coord', geo.bbo, 'mm'], \ 37 | ['bath_top_coord', geo.bto, 'mm'], \ 38 | ['nozzle_bottom_coord', geo.nbo, 'mm'],\ 39 | ['nozzle_center_x_coord', geo.ncx, 'mm'],\ 40 | ['nozzle_center_y_coord', geo.ncy, 'mm'], \ 41 | ['nozzle_angle', geo.na, 'degrees'], \ 42 | ['horizontal', geo.hor, ''], \ 43 | ['adjacent_filament_orientation', geo.adj, ''], \ 44 | ['adjacent_filament_offset', geo.dst, 'mm'], \ 45 | ['corresponding_simulation', geo.cor, ''], \ 46 | ['bath_velocity', geo.bv, 'm/s'], \ 47 | ['ink_velocity', geo.iv, 'm/s']] # RG 48 | s = "" 49 | for li in l: 50 | s = s + f'{li[0]}, {li[1]}, {li[2]}\n' 51 | self.s = s -------------------------------------------------------------------------------- /py/initialize/initialize_tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | # local packages 9 | 10 | 11 | # logging 12 | logging.basicConfig(level=logging.INFO) 13 | 14 | 15 | 16 | #------------------------------------------------------------------------------------------------- 17 | 18 | 19 | def scale() -> float: 20 | '''scale all units by this amount * m''' 21 | return 0.001 22 | 23 | 24 | class OpenFOAMFile: 25 | '''tools for creating openfoam files''' 26 | 27 | def __init__(self): 28 | return 29 | 30 | 31 | def header(self, cl:str, obj:str) -> str: 32 | '''header for openfoam files 33 | cl is the class name (string) 34 | obj is the object name (string) 35 | ''' 36 | s = ("/*--------------------------------*- C++ -*----------------------------------*\n" 37 | +"| ========= | |\n" 38 | +"| \\\\ / F ield | OpenFOAM: The Open Source CFD Toolbox |\n" 39 | +"| \\\\ / O peration | Version: 8 |\n" 40 | +"| \\\\ / A nd | Website: www.openfoam.com |\n" 41 | +"| \\\\/ M anipulation | |\n" 42 | +"*---------------------------------------------------------------------------*/\n" 43 | +"FoamFile\n" 44 | +"{\n" 45 | +" version 2.0;\n" 46 | +" format ascii;\n" 47 | +f" class {cl};\n" 48 | +f" object {obj};\n" 49 | +"}\n" 50 | +"// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //;\n\n" 51 | ) 52 | return s 53 | 54 | def closeLine(self) -> str: 55 | '''put this at the end of files''' 56 | return "// ************************************************************************* //"; -------------------------------------------------------------------------------- /py/initialize/ncreate3d.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | # external packages 5 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 6 | import logging 7 | import os,sys 8 | 9 | # local packages 10 | currentdir = os.path.dirname(os.path.realpath(__file__)) 11 | from initialize.creator_adjacent import * 12 | from initialize.creator import * 13 | 14 | #------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /py/initialize/transport_group.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions to generate OpenFOAM input files for a nozzle in a 3D bath''' 3 | 4 | 5 | # external packages 6 | from typing import List, Dict, Tuple, Union, Any, TextIO, Callable 7 | import logging 8 | import os,sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | from dict_list import DictList 15 | 16 | # logging 17 | logging.basicConfig(level=logging.INFO) 18 | 19 | #------------------------------------------------------------------------------------------------- 20 | 21 | class transportGroupNewt(DictList): 22 | '''gets a DictList object that describes the transport properties 23 | title is the name of the phase, e.g. 'ink' 24 | nu is the kinematic viscosity in m^2/s 25 | rho is the density in kg/m^3''' 26 | 27 | def __init__(self, title:str 28 | , nu:Union[float, str] 29 | , rho:Union[float, str]): 30 | super().__init__(title, 0, [["transportModel", "Newtonian"], ["nu", str(nu)], ["rho", str(rho)]]) 31 | 32 | 33 | class transportGroupHB(DictList): 34 | ''''transportGroupHB gets a DictList that describes Herschel-Bulkley transport properties 35 | title is the name of the phase 36 | the HB model follows nu = min(nu0, tau0/gammadot + k*gammadot^(n-1)) 37 | inputs to the model are nu0 in m^2/s, tau0 in m^2/s^2, k in m^2/s, n unitless 38 | rho is the density in kg/m^3''' 39 | 40 | def __init__(self, title:str 41 | , nu0:Union[float, str] 42 | , tau0:Union[float, str] 43 | , k:Union[float, str] 44 | , n:Union[float, str] 45 | , rho:Union[float, str]): 46 | super().__init__(title, 0, \ 47 | [["transportModel", "HerschelBulkley"], \ 48 | DictList("HerschelBulkleyCoeffs", 0, \ 49 | [["nu0", "[ 0 2 -1 0 0 0 0 ] " + str(nu0)],\ 50 | ["tau0", "[ 0 2 -2 0 0 0 0 ] " + str(tau0)], \ 51 | ["k", "[ 0 2 -1 0 0 0 0 ] " + str(k)],\ 52 | ["n", "[ 0 0 0 0 0 0 0 ] " + str(n)]]\ 53 | ),\ 54 | ["rho", str(rho)]]) -------------------------------------------------------------------------------- /py/plot/archive/README.txt: -------------------------------------------------------------------------------- 1 | These files have not been updated since the plotting files were restructured, so they are not compatible with the current version of the code. -------------------------------------------------------------------------------- /py/plot/archive/interface_plots.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''All of the plotting functions for plotting interface measurements.''' 3 | 4 | from plot_line import * 5 | from plot_pic import * 6 | from plot_steady import * 7 | from plot_slices import * 8 | from plot_metrics import * 9 | from plot_survival import * 10 | from plot_xs import * 11 | 12 | -------------------------------------------------------------------------------- /py/plot/archive/interfacemetrics.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Analyzing simulated single filaments''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import csv 8 | import numpy as np 9 | import pandas as pd 10 | from shapely.geometry import Polygon 11 | import re 12 | from typing import List, Dict, Tuple, Union, Any 13 | import logging 14 | import traceback 15 | 16 | # local packages 17 | currentdir = os.path.dirname(os.path.realpath(__file__)) 18 | parentdir = os.path.dirname(currentdir) 19 | sys.path.append(parentdir) 20 | import folder_parser as fp 21 | from pvCleanup import addUnits 22 | from plainIm import * 23 | import ncreate3d as nc 24 | from file_meta import folderStats 25 | import points_tools as pto 26 | 27 | # logging 28 | logger = logging.getLogger(__name__) 29 | logger.setLevel(logging.DEBUG) 30 | 31 | 32 | ########### FILE HANDLING ############## 33 | 34 | def takePlane(df:pd.DataFrame, folder:str, dr:float=0.05, xhalf:bool=True) -> pd.DataFrame: 35 | '''add an rbar column to the dataframe and only take the center plane''' 36 | fs = folderStats(folder) 37 | xc = fs.geo.ncx 38 | yc = fs.geo.ncy 39 | di = fs.geo.niw 40 | na = fs.geo.nozzle_angle 41 | tana = np.tan(np.deg2rad(na)) # tangent of nozzle angle 42 | zbot = fs.geo.nbz 43 | df['z'] =[zbot - i for i in df['z']] # put everything relative to the bottom of the nozzle 44 | ztop = fs.geo.nozzle_length 45 | df = df[df['z']>-ztop*0.9] # cut off the top 10% of the nozzle 46 | 47 | dy = di/3 # take middle y portion of nozzle 48 | df = df[(df['y']>-1*(dy))&(df['y']xc)] # back half of nozzle 51 | 52 | df['rbar'] = [np.sqrt((row['x']-xc)**2+(row['y']-yc)**2)/(di/2+abs(row['z'])*tana) for i,row in df.iterrows()] 53 | df['rbar'] = [round(int(rbar/dr)*dr,5) for rbar in df['rbar']] # round to the closest dr 54 | # radius as fraction of total radius at that z 55 | df = df[df['rbar']<0.95] 56 | 57 | return df 58 | 59 | 60 | def averageRings(df:pd.DataFrame) -> pd.DataFrame: 61 | '''given a list of points, group them by z and rbar, and take the average values by group''' 62 | vals = df.groupby(by=['z', 'rbar']) 63 | 64 | def removeOutliers(df:pd.DataFrame, col:str, sigma:int=3) -> pd.DataFrame: 65 | '''remove outliers from the list based on column col''' 66 | med = df[col].median() 67 | stdev = df[col].std() 68 | return df[(df[col]>med-sigma*stdev)&(df[col] varPlots: 34 | return superSummaryPlot(os.path.join(cfg.path.server, 'conical') 35 | , os.path.join(cfg.path.fig, 'conical') 36 | , 'nozzle_angle', ['arean', 'vertdispn', 'aspectratio', 'speeddecay'] 37 | , 2.5, 8, xunits='niw' 38 | , splitxvar = 'yvar' 39 | , **kwargs) 40 | -------------------------------------------------------------------------------- /py/plot/archive/plot_slices.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for plotting cross-sections''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import matplotlib 8 | import matplotlib.pyplot as plt 9 | import pandas as pd 10 | import numpy as np 11 | from typing import List, Dict, Tuple, Union, Any, TextIO 12 | import logging 13 | import traceback 14 | 15 | # local packages 16 | 17 | # logging 18 | logger = logging.getLogger(__name__) 19 | logger.setLevel(logging.DEBUG) 20 | for s in ['matplotlib', 'imageio', 'IPython', 'PIL']: 21 | logging.getLogger(s).setLevel(logging.WARNING) 22 | 23 | # plotting 24 | matplotlib.rcParams['svg.fonttype'] = 'none' 25 | matplotlib.rc('font', family='Arial') 26 | matplotlib.rc('font', size='10.0') 27 | 28 | 29 | #------------------------------------------- 30 | 31 | def plotSlices(data:pd.DataFrame) -> plt.Figure: 32 | '''use this to plot all slices within a pandas dataframe containing the points on the interface 33 | data is a pandas dataframe, e.g. one imported from importpoints''' 34 | d = data 35 | ticks = [-1, -0.5, 0, 0.5, 1] 36 | size = 8 37 | if len(xpts(data))>1: 38 | cmap = 'RdBu' 39 | color = 'x' 40 | sidelegend = True 41 | else: 42 | cmap = None 43 | color = 'Black' 44 | sidelegend = False 45 | p = d.plot.scatter(x='y', y='z', c=color, xlim=[-1, 1], ylim=[-1, 1], legend=False,\ 46 | colormap = cmap, figsize=[size, size], \ 47 | xticks=ticks, yticks=ticks, sharex=False, s=0.1) 48 | p.set_aspect('equal', adjustable='box') 49 | p.set_xlabel('y (mm)') 50 | p.set_ylabel('z (mm)') 51 | if sidelegend: 52 | f = plt.gcf() 53 | cax = f.get_axes()[1] 54 | cax.set_ylabel('x (mm)') 55 | cax.figsize=[0.5, size] 56 | return p 57 | 58 | # def plotXSOnAx(pts:pd.DataFrame, ax, color) -> None: 59 | # '''sort the points by radial position and plot as line''' 60 | # ptlists = [[]] 61 | 62 | # # split the points into discrete objects 63 | # for i,row in pts.iterrows(): 64 | # if i==0: 65 | # ptlists[0].append(dict(row)) 66 | # else: 67 | # j = 0 68 | # while j0.1: 69 | # # point too far away 70 | # j+=1 71 | # if j str: 38 | '''get a label for exporting file names''' 39 | self.label = f'cells' 40 | 41 | 42 | def metaItem(self, fs:folderStats) -> float: 43 | '''get the value to plot from the folderStats object. import the number of cells on the last step from the logReader table''' 44 | lr = logReader(fs.folder) 45 | if hasattr(lr, 'df'): 46 | row = lr.df.iloc[-1] 47 | return row['cells'] -------------------------------------------------------------------------------- /py/plot/colors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Plotting tools for analyzing OpenFOAM single filaments''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import numpy as np 8 | import matplotlib 9 | import matplotlib.pyplot as plt 10 | import matplotlib.patches as mpatches 11 | import pandas as pd 12 | import seaborn as sns 13 | import itertools 14 | from typing import List, Dict, Tuple, Union, Any, TextIO 15 | import logging 16 | import traceback 17 | 18 | # local packages 19 | currentdir = os.path.dirname(os.path.realpath(__file__)) 20 | parentdir = os.path.dirname(currentdir) 21 | sys.path.append(parentdir) 22 | import tools.strings as st 23 | 24 | # plotting 25 | matplotlib.rcParams['svg.fonttype'] = 'none' 26 | matplotlib.rc('font', family='Arial') 27 | matplotlib.rc('font', size='10.0') 28 | 29 | # logging 30 | logger = logging.getLogger(__name__) 31 | logger.setLevel(logging.DEBUG) 32 | for s in ['matplotlib', 'imageio', 'IPython', 'PIL']: 33 | logging.getLogger(s).setLevel(logging.WARNING) 34 | 35 | 36 | #------------------------------------------- 37 | def sigmaVelocityFunc(sigma:int, velocity:int) -> str: 38 | '''get a string representing this value''' 39 | if sigma==0: 40 | if velocity==0: 41 | return '#ad5555' 42 | else: 43 | return '#852113' 44 | else: 45 | if velocity==0: 46 | return '#52abcc' 47 | else: 48 | return '#1a5f78' 49 | 50 | class plotColors: 51 | '''for deciding on color values for plots''' 52 | 53 | def __init__(self, vallist:list, cvar:str, clabel:str, byIndices:bool=True, logScale:bool=False, defaultColor:str='#000000', **kwargs): 54 | self.vallist = vallist # list of values used to determine color 55 | self.cvar = cvar 56 | self.clabel = clabel 57 | self.defaultColor = defaultColor 58 | 59 | # explicitly set the bounds of the range used for value scaling 60 | if 'minval' in kwargs: 61 | self.minval = kwargs['minval'] 62 | else: 63 | self.minval = min(self.vallist) 64 | if 'maxval' in kwargs: 65 | self.maxval = kwargs['maxval'] 66 | else: 67 | self.maxval = max(self.vallist) 68 | 69 | # values are strings. no fractional scaling allowed 70 | if type(list(self.vallist)[0]) is str: 71 | byIndices=True 72 | 73 | if byIndices: 74 | # select based on the index in the list 75 | self.valFunc = self.indexFunc 76 | else: 77 | if logScale: 78 | # select based on log-scaled fractional value within a range 79 | self.valFunc = self.logFracFunc 80 | else: 81 | # select based on fractional value within a range 82 | self.valFunc = self.fracFunc 83 | 84 | if 'color' in kwargs: 85 | # always one color 86 | self.cfunc = self.oneColor 87 | self.valfunc = self.exactFunc 88 | self.color = kwargs['color'] 89 | elif 'colorList' in kwargs: 90 | # select value from list of colors 91 | self.colorList = kwargs['colorList'] 92 | self.cfunc = self.listFunc 93 | self.valFunc = self.indexFunc 94 | elif 'colorDict' in kwargs: 95 | # select value from dictionary of colors 96 | self.colorDict = kwargs['colorDict'] 97 | self.cfunc = self.dictFunc 98 | self.valFunc = self.exactFunc 99 | elif 'cname' in kwargs: 100 | # select value from color palette 101 | self.cname = kwargs['cname'] 102 | if self.cname=='cubeHelix': 103 | self.makeCubeHelix() 104 | elif self.cname=='diverging': 105 | self.makeDiverging() 106 | else: 107 | self.makePalette() 108 | elif self.cvar=='sigma': 109 | # use specific colors for interfacial tension 110 | self.colorDict = {0:'#940f0f', 40:'#61bab0', 70:'#1e5a85'} 111 | self.cfunc = self.dictFunc 112 | self.valFunc = self.exactFunc 113 | else: 114 | # use one color 115 | if len(self.vallist)==1: 116 | self.color = 'black' 117 | self.cfunc = self.oneColor 118 | self.valFunc = self.exactFunc 119 | else: 120 | self.cname = 'viridis' 121 | self.makePalette() 122 | 123 | 124 | 125 | 126 | def fracFunc(self, val:Any) -> float: 127 | '''get the position of the value scaled by value as a fraction from 0 to 1''' 128 | return (val-self.minval)/(self.maxval-self.minval) 129 | 130 | def logFracFunc(self, val:Any) -> float: 131 | return (np.log10(val)-np.log10(self.minval))/(np.log10(self.maxval)-np.log10(self.minval)) 132 | 133 | def indexFunc(self, val:Any) -> float: 134 | '''get the position of the value scaled by order in the list as a fraction from 0 to 1''' 135 | if not val in self.vallist: 136 | raise ValueError(f'Color value {val} is not in color value list {self.vallist}') 137 | 138 | return self.vallist.index(val)/len(self.vallist) 139 | 140 | def exactFunc(self, val:Any) -> Any: 141 | return val 142 | 143 | #------------------------- 144 | 145 | def oneColor(self, val:Any) -> str: 146 | '''always return the same color''' 147 | return self.color 148 | 149 | def listFunc(self, val:Any) -> str: 150 | ''' get a color from a list of values ''' 151 | i = int(val*len(self.vallist)) 152 | if i<0 or i>len(self.colorList): 153 | return self.defaultColor 154 | return self.colorList[i] 155 | 156 | def dictFunc(self, val:Any) -> str: 157 | '''get a color from a dictionary''' 158 | if not val in self.colorDict: 159 | return self.defaultColor 160 | return self.colorDict[val] 161 | 162 | def makeCubeHelix(self): 163 | self.cmap = sns.cubehelix_palette(as_cmap=True, rot=-0.4) 164 | self.cfunc = self.cmapFunc 165 | 166 | def makeDiverging(self): 167 | self.cmap = sns.diverging_palette(220, 20, as_cmap=True) 168 | self.cfunc = self.cmapFunc 169 | 170 | def makePalette(self): 171 | self.cmap = sns.color_palette(self.cname, as_cmap=True) 172 | self.cfunc = self.cmapFunc 173 | 174 | def cmapFunc(self, val:Any) -> str: 175 | if val<0 or val>1: 176 | return self.defaultColor 177 | cmap = self.cmap 178 | return cmap(val) 179 | 180 | #------------------------- 181 | 182 | def getColor(self, val): 183 | return self.cfunc(self.valFunc(val)) 184 | -------------------------------------------------------------------------------- /py/plot/convergence_plot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Plotting tools for plotting everything on one axis''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import numpy as np 8 | import matplotlib 9 | import matplotlib.pyplot as plt 10 | import matplotlib.patches as mpatches 11 | import pandas as pd 12 | import seaborn as sns 13 | import itertools 14 | from typing import List, Dict, Tuple, Union, Any, TextIO 15 | import logging 16 | import traceback 17 | 18 | # local packages 19 | currentdir = os.path.dirname(os.path.realpath(__file__)) 20 | parentdir = os.path.dirname(currentdir) 21 | sys.path.append(parentdir) 22 | from plot.multi_plot import multiPlot 23 | from summarize.log_reader import logReader 24 | 25 | # plotting 26 | matplotlib.rcParams['svg.fonttype'] = 'none' 27 | matplotlib.rc('font', family='Arial') 28 | matplotlib.rc('font', size='10.0') 29 | 30 | # logging 31 | logger = logging.getLogger(__name__) 32 | logger.setLevel(logging.DEBUG) 33 | for s in ['matplotlib', 'imageio', 'IPython', 'PIL']: 34 | logging.getLogger(s).setLevel(logging.WARNING) 35 | 36 | 37 | #------------------------------------------- 38 | 39 | class convergencePlot(multiPlot): 40 | '''for plotting residuals vs. time, for various folders''' 41 | 42 | def __init__(self, topFolder:Union[List[str], str], exportFolder:str, yvar:str, **kwargs): 43 | super().__init__(topFolder, exportFolder, 'simTime', yvar, **kwargs) # feed initialization dummy variables because xvar and yvar might not be folderStats attributes 44 | self.units = {} 45 | self.plot() 46 | self.clean() 47 | 48 | if self.export: 49 | self.exportIm(**kwargs) 50 | 51 | 52 | def getFileLabel(self) -> str: 53 | '''get a label for exporting file names''' 54 | self.label = f'convergence_{self.yvarreal}' 55 | 56 | def plotFolderAx(self, ax, row:pd.Series) -> None: 57 | '''plot all lines for one folder on the axis''' 58 | lr = logReader(row['folder']) 59 | df = lr.df 60 | self.units = {**self.units, **lr.u} 61 | 62 | color = self.colors.getColor(row['cvar']) 63 | df=df[df[self.yvarreal]>0] 64 | ylist = df[self.yvarreal] 65 | if not self.scaling==1: 66 | ylist = ylist/self.scaling 67 | ax.plot(df['simtime'], ylist, color=color, marker=None, lw=0.5) 68 | 69 | def plotAx(self, row:int, col:int): 70 | '''plot values on the axis''' 71 | ax = self.axs[row][col] 72 | folders = self.selectFiles(row,col) 73 | for i,row in folders.iterrows(): 74 | self.plotFolderAx(ax, row) 75 | 76 | def plot(self): 77 | '''plot the xvar and yvar on each axis''' 78 | for row in range(self.nrow): 79 | for col in range(self.ncol): 80 | self.plotAx(row, col) 81 | -------------------------------------------------------------------------------- /py/plot/folder_plots.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Plotting tools for analyzing OpenFOAM single filaments''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import numpy as np 8 | import matplotlib 9 | import matplotlib.pyplot as plt 10 | import matplotlib.patches as mpatches 11 | import pandas as pd 12 | import seaborn as sns 13 | import itertools 14 | from typing import List, Dict, Tuple, Union, Any, TextIO 15 | import logging 16 | import traceback 17 | 18 | # local packages 19 | currentdir = os.path.dirname(os.path.realpath(__file__)) 20 | parentdir = os.path.dirname(currentdir) 21 | sys.path.append(parentdir) 22 | import tools.strings as st 23 | from folder_stats import folderStats 24 | from plot.sizes import sizes 25 | from plot.var_plots import varPlots 26 | 27 | # plotting 28 | matplotlib.rcParams['svg.fonttype'] = 'none' 29 | matplotlib.rc('font', family='Arial') 30 | matplotlib.rc('font', size='10.0') 31 | 32 | # logging 33 | logger = logging.getLogger(__name__) 34 | logger.setLevel(logging.DEBUG) 35 | for s in ['matplotlib', 'imageio', 'IPython', 'PIL']: 36 | logging.getLogger(s).setLevel(logging.WARNING) 37 | 38 | #------------------------------------------- 39 | 40 | class folderPlots(varPlots): 41 | '''A generic class used for plotting many folders at once. Subclasses are comboPlot, which puts everything on one plot, and gridOfPlots, which puts everything in separate plots based on viscosity.''' 42 | 43 | def __init__(self, topFolder:str 44 | , adjustBounds:bool=True 45 | , **kwargs): 46 | super().__init__(topFolder, **kwargs) 47 | self.ab = adjustBounds 48 | self.getIndices() 49 | 50 | 51 | 52 | #------------------------------------------------------------------- 53 | 54 | def getIndices(self): 55 | '''get the position of each folder in each list''' 56 | 57 | # fill in indices 58 | for s in self.varlist: 59 | l = getattr(self, f'{s}list') 60 | self.filedf[f'{s}index'] = [l.index(i) for i in self.filedf[f'{s}var']] 61 | 62 | def getPos(self, fs:folderStats) -> dict: 63 | '''get the position in the plot for a folder''' 64 | folder = fs.folder 65 | row = self.filedf[self.filedf.folder==folder].iloc[0] 66 | return dict([[f'{s}index', row[f'{s}index']] for s in ['x', 'y', 'c', 'splitx', 'splity']]) 67 | 68 | def getXYRow(self, row:pd.Series) -> dict: 69 | '''get the x,y,color, and split of the folder from the row in self.filedf''' 70 | x0 = self.xmlist[row['xindex']] 71 | y0 = self.ymlist[row['yindex']] 72 | color = self.colors.getColor(row['cvar']) 73 | axcol = row['splitxindex'] 74 | axrow = row['splityindex'] 75 | ax = self.axs[axrow][axcol] 76 | self.indicesreal = self.indicesreal.append({'x':row['xindex'], 'y':row['yindex'], 'color':row['cindex'], 'axx':axcol, 'axy':axrow}, ignore_index=True) 77 | self.xlistreal.append(x0) 78 | self.ylistreal.append(y0) 79 | self.clistreal.append(color) 80 | return {'x0':x0, 'y0':y0, 'color':color, 'ax':ax} 81 | 82 | #------------------------------------------------------------------- 83 | 84 | def putAbove(self, axcol:int, axrow:int=0) -> Tuple[int,int,float,float]: 85 | '''get the indices and positions for the ideal plot to put the plot above the top left corner''' 86 | ir = self.indicesreal[(self.indicesreal.axx==axcol)&(self.indicesreal.axy==axrow)] # select the rows in this axis 87 | xind = int(ir.x.min()) 88 | x0 = self.xmlist[xind] 89 | yind = int(ir[ir.x==xind].y.max()) 90 | y0 = self.ymlist[yind]+self.dy 91 | yind = yind+1 92 | return xind, yind, x0, y0 93 | 94 | def putLeft(self, axcol:int, axrow:int=0) -> Tuple[int,int,float,float]: 95 | '''get the indices to put the plot left of the bottom left corner''' 96 | ir = self.indicesreal[(self.indicesreal.axx==axcol)&(self.indicesreal.axy==axrow)] # select the rows in this axis 97 | xind = int(ir.x.min()) 98 | x0 = self.xmlist[xind]-self.dx 99 | xind = xind-1 100 | yind = int(ir.y.max()) 101 | y0 = self.ymlist[yind] 102 | return xind, yind, x0, y0 -------------------------------------------------------------------------------- /py/plot/markers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Plotting tools for analyzing OpenFOAM single filaments''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import numpy as np 8 | import matplotlib 9 | import matplotlib.pyplot as plt 10 | import matplotlib.patches as mpatches 11 | import pandas as pd 12 | import seaborn as sns 13 | import itertools 14 | from typing import List, Dict, Tuple, Union, Any, TextIO 15 | import logging 16 | import traceback 17 | 18 | # local packages 19 | currentdir = os.path.dirname(os.path.realpath(__file__)) 20 | parentdir = os.path.dirname(currentdir) 21 | sys.path.append(parentdir) 22 | import tools.strings as st 23 | 24 | # plotting 25 | matplotlib.rcParams['svg.fonttype'] = 'none' 26 | matplotlib.rc('font', family='Arial') 27 | matplotlib.rc('font', size='10.0') 28 | 29 | # logging 30 | logger = logging.getLogger(__name__) 31 | logger.setLevel(logging.DEBUG) 32 | for s in ['matplotlib', 'imageio', 'IPython', 'PIL']: 33 | logging.getLogger(s).setLevel(logging.WARNING) 34 | 35 | 36 | #------------------------------------------- 37 | 38 | class plotMarkers: 39 | '''for deciding on marker values for plots''' 40 | 41 | def __init__(self, vallist:list, mvar:str, mlabel:str 42 | , markerSize:int, line:bool 43 | , markerList:list=['o','v','s','^','X','D', 'P', '<', '>', '8', 'p', 'h', 'H'] 44 | , lineList:list = ['solid', 'dotted', 'dashed', 'dashdot'] 45 | , **kwargs): 46 | self.vallist = vallist # list of values used to determine color 47 | self.mvar = mvar 48 | self.mlabel = mlabel 49 | self.markerSize = markerSize 50 | self.markerList = markerList 51 | self.lineList = lineList 52 | self.line = line 53 | 54 | if mvar=='const': 55 | if not 'marker' in kwargs: 56 | kwargs['marker'] = self.markerList[0] 57 | if not 'line' in kwargs: 58 | kwargs['line'] = self.lineList[0] 59 | if 'marker' in kwargs: 60 | self.mfunc = self.constMarker 61 | self.mvalFunc = self.constFunc 62 | self.marker = kwargs['marker'] 63 | else: 64 | self.mfunc = self.listMarker 65 | self.mvalFunc = self.indexFunc 66 | 67 | if 'markerDict' in kwargs: 68 | self.mfunc = self.dictMarker 69 | self.mvalFunc = self.exactFunc 70 | self.mDict = kwargs['markerDict'] 71 | 72 | if not line: 73 | self.lfunc = self.constLine 74 | self.lvalFunc = self.constFunc 75 | self.line0 = 'None' 76 | else: 77 | if 'lineStyle' in kwargs: 78 | self.lfunc = self.constLine 79 | self.lvalFunc = self.constFunc 80 | self.line = kwargs['lineStyle'] 81 | elif 'lineDict' in kwargs: 82 | self.lfunc = self.dictLine 83 | self.lvalFunc = self.exactFunc 84 | self.lDict = kwargs['lineDict'] 85 | else: 86 | self.lfunc = self.listLine 87 | self.lvalFunc = self.indexFunc 88 | 89 | #--------------------------- 90 | 91 | def indexFunc(self, val:Any) -> float: 92 | '''get the index of this value in the list''' 93 | if not val in self.vallist: 94 | return 0 95 | else: 96 | return self.vallist.index(val) 97 | 98 | def constFunc(self, val:Any) -> float: 99 | return 0 100 | 101 | def exactFunc(self, val:Any) -> Any: 102 | return val 103 | 104 | #---------------------------- 105 | 106 | def listMarker(self, val): 107 | '''get the marker from a list''' 108 | return self.markerList[val] 109 | 110 | def constMarker(self, val): 111 | '''always return the same marker''' 112 | return self.marker 113 | 114 | def dictMarker(self, val): 115 | '''get the marker from a dictionary''' 116 | return self.mDict[val] 117 | 118 | #----------------------------- 119 | 120 | def listLine(self, val): 121 | '''get the line from a list''' 122 | return self.lineList[val] 123 | 124 | def constLine(self, val): 125 | '''always return the same line''' 126 | return self.line 127 | 128 | def dictLine(self, val): 129 | '''get the line style from a dictionary''' 130 | return self.lDict[val] 131 | 132 | #----------------------------- 133 | 134 | def getMarker(self, val): 135 | '''get the marker from a value''' 136 | return self.mfunc(self.mvalFunc(val)) 137 | 138 | def getLine(self, val): 139 | '''get the line style from a value''' 140 | if not self.line: 141 | return 'None' 142 | return self.lfunc(self.lvalFunc(val)) 143 | -------------------------------------------------------------------------------- /py/plot/measurement_plot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for plotting values from simulations''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | from typing import List, Dict, Tuple, Union, Any, TextIO 8 | import logging 9 | import traceback 10 | import pandas as pd 11 | import numpy as np 12 | import matplotlib.pyplot as plt 13 | from plot.colors import plotColors 14 | 15 | # local packages 16 | currentdir = os.path.dirname(os.path.realpath(__file__)) 17 | parentdir = os.path.dirname(currentdir) 18 | sys.path.append(parentdir) 19 | from plot.value_plot import valuePlot 20 | from folder_stats import folderStats 21 | from points.folder_points import folderPoints 22 | from plainIm import * 23 | 24 | # logging 25 | logger = logging.getLogger(__name__) 26 | logger.setLevel(logging.DEBUG) 27 | for s in ['matplotlib', 'imageio', 'IPython', 'PIL']: 28 | logging.getLogger(s).setLevel(logging.WARNING) 29 | 30 | 31 | #------------------------------------------- 32 | 33 | 34 | class measurementPlot(valuePlot): 35 | '''plot a circle or square representing a value from the folderStats 36 | topFolder is the folder that holds all the files 37 | overwrite True to overwrite plots 38 | logValues to plot the values on a log scale 39 | shape can be square or circle 40 | ''' 41 | 42 | def __init__(self, topFolder:str 43 | , exportFolder:str 44 | , var:str 45 | , time:float 46 | , xbehind:float 47 | , xunits:str='mm' 48 | , **kwargs): 49 | self.time = time 50 | self.xbehind = xbehind 51 | self.xunits = xunits 52 | self.var = var 53 | self.fPoints = {} 54 | # dictionary of folderPoints objects 55 | super().__init__(topFolder, exportFolder, timeplot=False, shape='square', gridlines=False, **kwargs) 56 | 57 | 58 | def getFileLabel(self) -> str: 59 | '''get a label for exporting file names''' 60 | self.label = f'meas_{self.var}_{self.xbehind}{self.xunits}_t_{self.time}' 61 | 62 | 63 | def metaItem(self, fs:folderStats) -> float: 64 | '''get the value to plot from the folderStats object. holder text, replace this for subclasses''' 65 | if fs.folder in self.fPoints: 66 | fp = self.fPoints[fs.folder] 67 | else: 68 | fp = folderPoints(fs) 69 | self.fPoints[fs.folder] = fp 70 | 71 | row, u = fp.importSummarySlice(self.time, self.xbehind, self.xunits) 72 | if len(row)==0: 73 | return np.nan 74 | if not hasattr(self, 'units'): 75 | self.units = u 76 | if not 'behind' in self.fnc.figTitle: 77 | xunname = self.xunits.replace('nozzle_inner_width', '$d_i$') 78 | l, style = self.getLabel(self.var) 79 | self.fnc.figTitle = f'{l}, {self.xbehind} {xunname} behind nozzle, t = {self.time} s' 80 | row = row.iloc[0] 81 | return row[self.var] 82 | -------------------------------------------------------------------------------- /py/plot/meta_plot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for plotting names of simulations for easy referencing''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | from typing import List, Dict, Tuple, Union, Any, TextIO 8 | import logging 9 | import traceback 10 | 11 | # local packages 12 | currentdir = os.path.dirname(os.path.realpath(__file__)) 13 | parentdir = os.path.dirname(currentdir) 14 | sys.path.append(parentdir) 15 | from plot.combo_plot import comboPlot 16 | from folder_stats import folderStats 17 | 18 | # logging 19 | logger = logging.getLogger(__name__) 20 | logger.setLevel(logging.DEBUG) 21 | for s in ['matplotlib', 'imageio', 'IPython', 'PIL']: 22 | logging.getLogger(s).setLevel(logging.WARNING) 23 | 24 | 25 | #------------------------------------------- 26 | 27 | class metaPlot(comboPlot): 28 | '''plot as text a single piece of metadata from the folderStats 29 | topFolder is the folder that holds all the files 30 | overwrite True to overwrite plots 31 | ''' 32 | 33 | def __init__(self, topFolder:str 34 | , exportFolder:str 35 | , **kwargs): 36 | super().__init__(topFolder, exportFolder=exportFolder, **kwargs) 37 | self.getFN(**kwargs) 38 | if not self.checkOverwrite(export=self.export, overwrite=self.overwrite): 39 | return 40 | 41 | # iterate through folders and plot data 42 | for i,row in self.filedf.iterrows(): 43 | self.plotFolder(row) 44 | 45 | self.clean() 46 | 47 | if self.export: 48 | self.exportIm(**kwargs) 49 | 50 | #------------------- 51 | 52 | def getFileLabel(self) -> str: 53 | '''get a label for exporting file names''' 54 | self.label = f'meta' 55 | 56 | 57 | def plotFolder(self, row) -> None: 58 | '''given a row in the pandas dataframe, plot the slices''' 59 | # identify if we need to add a new ideal plot 60 | fs = self.fstats[row['folder']] 61 | # plot basename 62 | pos = self.getXYRow(row) 63 | xm = pos['x0'] 64 | ym = pos['y0'] + ((row['cindex']+1)/(len(self.clist)+1) - 0.5)*(self.yr[1]-self.yr[0]) # shift vertically if we have multiple colors 65 | pos['ax'].text(xm, ym, self.metaItem(fs), horizontalalignment='center', verticalalignment='center', c=pos['color']) 66 | 67 | def metaItem(self, fs:folderStats) -> str: 68 | '''get the text to plot from the folderStats object. holder text, replace this for subclasses''' 69 | return '' 70 | -------------------------------------------------------------------------------- /py/plot/plot_tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''General plotting tools''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import csv 8 | import numpy as np 9 | import pandas as pd 10 | import matplotlib.pyplot as plt 11 | import re 12 | from typing import List, Dict, Tuple, Union, Any 13 | import logging 14 | import traceback 15 | 16 | # local packages 17 | currentdir = os.path.dirname(os.path.realpath(__file__)) 18 | parentdir = os.path.dirname(currentdir) 19 | sys.path.append(parentdir) 20 | 21 | # logging 22 | logger = logging.getLogger(__name__) 23 | logger.setLevel(logging.DEBUG) 24 | 25 | 26 | 27 | ################################################################# 28 | 29 | def plotCircle(ax:plt.Axes, x0:float, y0:float, radius:float, caption:str, color, sigmapos:int=0) -> None: 30 | '''plotCircle plots a circle, with optional caption. 31 | ax is axis to plot on 32 | x0 is the x position 33 | y0 is the y position 34 | radius is the radius of the circle, in plot coords 35 | caption is the label. use '' to have no caption 36 | color is the color of the circle 37 | sigmapos is the position in the sigma list for this circle. Useful for timeplots, so labels don't stack on top of each other. If we're using this to plot an ideal cross-section or some other sigma-unaffiliated value, sigmapos=0 will put the label inside the circle or just above it.''' 38 | 39 | circle = plt.Circle([x0,y0], radius, color=color, fill=False) # create the circle 40 | ax.add_artist(circle) # put the circle on the plot 41 | 42 | if len(caption)>0: 43 | if radius>0.3: 44 | # if the circle is big, put the label inside 45 | txtx = x0 46 | txty = y0+0.2*sigmapos 47 | ax.text(txtx, txty, caption, horizontalalignment='center', verticalalignment='center', color=color) 48 | else: 49 | # if the circle is small, put the label outside 50 | angle=(90-30*sigmapos)*(2*np.pi/360) # angle to put the label at, in rad 51 | dar = 0.2 # length of arrow 52 | arrowx = x0+radius*np.cos(angle) # where the arrow points 53 | arrowy = y0+radius*np.sin(angle) 54 | txtx = arrowx+dar*np.cos(angle) # where the label is 55 | txty = arrowy+dar*np.sin(angle) 56 | ax.annotate(caption, (arrowx, arrowy), color=color, xytext=(txtx, txty), ha='center', arrowprops={'arrowstyle':'->', 'color':color}) 57 | 58 | 59 | 60 | def setSquare(ax): 61 | '''make the axis square''' 62 | ax.set_aspect(1.0/ax.get_data_ratio(), adjustable='box') -------------------------------------------------------------------------------- /py/plot/rate_plot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for plotting values from simulations''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | from typing import List, Dict, Tuple, Union, Any, TextIO 8 | import logging 9 | import traceback 10 | 11 | # local packages 12 | currentdir = os.path.dirname(os.path.realpath(__file__)) 13 | parentdir = os.path.dirname(currentdir) 14 | sys.path.append(parentdir) 15 | from plot.value_plot import valuePlot 16 | from folder_stats import folderStats 17 | 18 | # logging 19 | logger = logging.getLogger(__name__) 20 | logger.setLevel(logging.DEBUG) 21 | for s in ['matplotlib', 'imageio', 'IPython', 'PIL']: 22 | logging.getLogger(s).setLevel(logging.WARNING) 23 | 24 | 25 | #------------------------------------------- 26 | 27 | class ratePlot(valuePlot): 28 | '''plot the simulation rates as circles''' 29 | 30 | def __init__(self, topFolder:str 31 | , exportFolder:str 32 | , **kwargs): 33 | super().__init__(topFolder, exportFolder, timeplot=True, shape='circle', logValues=False, minval=0, maxval=300, **kwargs) 34 | self.figtitle = f'Simulation rate (real hr/sim s)' 35 | 36 | def getFileLabel(self) -> str: 37 | '''get a label for exporting file names''' 38 | self.label = f'rate' 39 | 40 | 41 | def metaItem(self, fs:folderStats) -> float: 42 | '''get the value to plot from the folderStats object. holder text, replace this for subclasses''' 43 | sr = fs.simulation_rate 44 | if sr=='': 45 | return np.nan 46 | else: 47 | return float(sr) -------------------------------------------------------------------------------- /py/plot/sizes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Plotting tools for analyzing OpenFOAM single filaments''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import numpy as np 8 | from typing import List, Dict, Tuple, Union, Any, TextIO 9 | import logging 10 | import traceback 11 | 12 | # local packages 13 | currentdir = os.path.dirname(os.path.realpath(__file__)) 14 | parentdir = os.path.dirname(currentdir) 15 | sys.path.append(parentdir) 16 | 17 | # logging 18 | logger = logging.getLogger(__name__) 19 | logger.setLevel(logging.DEBUG) 20 | for s in ['matplotlib', 'imageio', 'IPython', 'PIL']: 21 | logging.getLogger(s).setLevel(logging.WARNING) 22 | 23 | 24 | #------------------------------------------- 25 | 26 | class sizes: 27 | '''for setting sizes of figures, fonts, and markers''' 28 | 29 | def __init__(self, rows:int, cols:int, plotType:str='notebook'): 30 | self.rows = rows 31 | self.cols = cols 32 | self.plotType = plotType 33 | if self.plotType=='ppt': 34 | self.fs = 18 35 | self.getFigSize(14, 7) 36 | self.markersize=100 37 | self.linewidth = 2 38 | elif self.plotType=='paper': 39 | self.fs = 8 40 | self.getFigSize(6.5, 8.5) 41 | self.markersize=20 42 | self.linewidth = 1 43 | elif self.plotType=='notebook': 44 | self.fs = 10 45 | self.getFigSize(10, 10) 46 | self.markersize = 40 47 | self.linewidth = 2 48 | else: 49 | raise ValueError(f'Unknown plot type {self.plotType}') 50 | 51 | def values(self): 52 | return self.fs, self.figsize, self.markersize, self.linewidth 53 | 54 | 55 | def getFigSize(self, wmax:float, hmax:float) -> None: 56 | self.ar = self.rows/self.cols 57 | wider = [wmax, wmax*self.ar] 58 | if wider[1]>hmax: 59 | wider = [w*hmax/wider[1] for w in wider] 60 | self.figsize = tuple(wider) -------------------------------------------------------------------------------- /py/plot/super_summary_plot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Analyzing simulated single filaments''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import csv 8 | import numpy as np 9 | import pandas as pd 10 | import re 11 | from typing import List, Dict, Tuple, Union, Any 12 | import matplotlib 13 | import colorsys 14 | import logging 15 | import traceback 16 | 17 | # local packages 18 | currentdir = os.path.dirname(os.path.realpath(__file__)) 19 | parentdir = os.path.dirname(currentdir) 20 | sys.path.append(parentdir) 21 | from plot.multi_plot import multiPlot 22 | from summarize.super_summary import superSummary 23 | 24 | 25 | # logging 26 | logger = logging.getLogger(__name__) 27 | logger.setLevel(logging.DEBUG) 28 | 29 | 30 | #------------------------------------------------------ 31 | 32 | class superSummaryPlot(multiPlot): 33 | '''plot data from the supersummary''' 34 | 35 | def __init__(self, topFolder:Union[List[str], str] 36 | , exportFolder:str 37 | , ssFolder:str 38 | , xvar:Union[str, List[str]] 39 | , yvar:Union[str, List[str]] 40 | , time:float 41 | , xbehind:float 42 | , xunits:str='niw' 43 | , reference:bool=True 44 | , referenceStyle:str='scatter' 45 | , refLoc:str='left' 46 | , **kwargs): 47 | self.ssFolder = ssFolder 48 | self.time = time 49 | self.xbehind = xbehind 50 | self.xunits = xunits 51 | self.reference = reference 52 | self.referenceStyle=referenceStyle 53 | self.refLoc = refLoc 54 | super().__init__(topFolder, exportFolder, xvar, yvar, **kwargs) 55 | self.ss = superSummary(topFolder, ssFolder, time, xbehind, xunits) 56 | self.ssRef = superSummary(topFolder, ssFolder, time, -3, 'niw') # get a reference slice for the initial values 57 | 58 | self.ss.importFile() # import or create values 59 | self.ssRef.importFile() 60 | self.units = self.ss.units 61 | 62 | self.plot() 63 | self.clean() 64 | if self.export: 65 | self.exportIm(**kwargs) 66 | 67 | def combine(self, var:Union[list, str]) -> None: 68 | if type(var) is list: 69 | return ','.join(var) 70 | else: 71 | return var 72 | 73 | def getFileLabel(self) -> str: 74 | '''get a label for exporting file names''' 75 | self.label = f'summary_{self.combine(self.xvarreal)}_{self.combine(self.yvarreal)}_{self.xbStr()}{self.xunits}_t_{self.time}' 76 | 77 | 78 | def plotAx(self, row:int, col:int) -> None: 79 | '''plot values on a single axis''' 80 | v = self.varTableRow(row, col) # get the names of the x and y variables 81 | xvar = v['x'] 82 | yvar = v['y'] 83 | 84 | # filter the list to just the folders in this splitval 85 | folders = self.selectFiles(row,col) 86 | 87 | # split by color and plot scatter 88 | for cval in self.clist: 89 | for mval in self.mlist: 90 | foldersi = folders[(folders.cvar==cval)&(folders.mvar==mval)] # narrow to the folders for this color 91 | if len(foldersi)>0: 92 | ax = self.axs[row][col] # determine which axis to plot on 93 | color = self.colors.getColor(cval) # the color for this color value 94 | marker = self.markers.getMarker(mval) 95 | self.plotPoints(ax, foldersi, xvar, yvar, color, marker, mval) 96 | # add a reference value ahead of the nozzle 97 | if self.reference: 98 | self.addReference(ax, foldersi, xvar, yvar, color, marker, cval, mval) 99 | self.addIdeal(ax, yvar) 100 | self.separateRef(ax, xvar) 101 | 102 | def separateRef(self, ax, xvar:str): 103 | '''add a vertical line to separate the reference from the actual points''' 104 | if self.reference and self.referenceStyle=='scatter': 105 | ax.axvline(self.xout(xvar, 0.5), ls='--', c='gray', lw=0.75) 106 | 107 | def plotPoints(self, ax, foldersi:pd.DataFrame, xvar:str, yvar:str, color, marker, mval): 108 | '''plot the points and line on the plot''' 109 | df = self.ss.df 110 | ss = df[df.folder.isin(foldersi.folder)] # get the data for these folders 111 | ax.scatter(ss[xvar], ss[yvar], edgecolor=color, facecolor='None', marker=marker, s=self.markerSize, linewidths=1) 112 | if self.line: 113 | ss = ss.sort_values(by=xvar) 114 | ls = self.markers.getLine(mval) 115 | ax.plot(ss[xvar], ss[yvar], color=color, marker=None, linestyle=ls) 116 | 117 | def xout(self, xvar:str, xfrac:float): 118 | '''get a value on the right edge of the plot''' 119 | df = self.ss.df 120 | xl = sorted(list(df[xvar].unique())) 121 | if len(xl)==1: 122 | dx = 0.1 123 | else: 124 | dx = min(np.diff(xl)) 125 | if self.refLoc=='right': 126 | return xl[-1]+dx*xfrac 127 | else: 128 | return xl[0]-dx*xfrac 129 | 130 | 131 | def addReference(self, ax, foldersi:pd.DataFrame, xvar:str, yvar:str, color, marker, cval, mval): 132 | '''add a reference point at the left to compare these values to the values ahead of the nozzle''' 133 | f0 = foldersi.iloc[0]['folder'] 134 | if not self.fstats[f0].ink.dynamic['v']==0: 135 | # only draw a reference line if there is no flow 136 | return 137 | dfref = self.ssRef.df 138 | ssref = dfref[dfref.folder==f0] 139 | if len(ssref)==0: 140 | return 141 | if self.referenceStyle=='line': 142 | # plot a horizontal line across the whole plot 143 | ax.axhline(ssref.iloc[0][yvar], color=color, linestyle='dashed', lw=1) 144 | else: 145 | # plot one point on the left or right edge 146 | ax.scatter([self.xout(xvar, 1.4)], [ssref.iloc[0][yvar]], color=color, edgecolor='None', marker='X', s=self.markerSize) 147 | self.legend.addReferenceHandle(color, cval, mval) 148 | 149 | def makeGrayer(self, color): 150 | '''lower the saturation of the given color''' 151 | h,l,s = colorsys.rgb_to_hls(*matplotlib.colors.ColorConverter.to_rgb(color)) 152 | return colorsys.hls_to_rgb(h,l,s*0.5) 153 | 154 | def plot(self): 155 | '''plot the xvar and yvar on each axis''' 156 | for row in range(self.nrow): 157 | for col in range(self.ncol): 158 | self.plotAx(row, col) 159 | -------------------------------------------------------------------------------- /py/plot/time_plot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for plotting in-simulation progress times of simulations for easy referencing''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | from typing import List, Dict, Tuple, Union, Any, TextIO 8 | import logging 9 | import traceback 10 | 11 | # local packages 12 | currentdir = os.path.dirname(os.path.realpath(__file__)) 13 | parentdir = os.path.dirname(currentdir) 14 | sys.path.append(parentdir) 15 | from plot.meta_plot import metaPlot 16 | from folder_stats import folderStats 17 | 18 | # logging 19 | logger = logging.getLogger(__name__) 20 | logger.setLevel(logging.DEBUG) 21 | for s in ['matplotlib', 'imageio', 'IPython', 'PIL']: 22 | logging.getLogger(s).setLevel(logging.WARNING) 23 | 24 | 25 | #------------------------------------------- 26 | 27 | class timePlot(metaPlot): 28 | '''plot the simulation folder basenames 29 | topFolder is the folder that holds all the files 30 | overwrite True to overwrite plots 31 | ''' 32 | 33 | def __init__(self, topFolder:str 34 | , exportFolder:str 35 | , xr:List[float] = [-0.5, 0.5] 36 | , yr:List[float] = [-0.7, 0.7] 37 | , **kwargs): 38 | super().__init__(topFolder, exportFolder=exportFolder, xr=xr, yr=yr, **kwargs) 39 | 40 | def getFileLabel(self) -> str: 41 | '''get a label for exporting file names''' 42 | self.label = f'name' 43 | 44 | def metaItem(self, fs:folderStats) -> str: 45 | '''get the text to plot from the folderStats object. holder text, replace this for subclasses''' 46 | return str(fs.simulation_time) 47 | -------------------------------------------------------------------------------- /py/plot/trace_plots.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Plotting tools for plotting everything on one axis''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import numpy as np 8 | import matplotlib 9 | import matplotlib.pyplot as plt 10 | import matplotlib.patches as mpatches 11 | import pandas as pd 12 | import seaborn as sns 13 | import itertools 14 | from typing import List, Dict, Tuple, Union, Any, TextIO 15 | import logging 16 | import traceback 17 | 18 | # local packages 19 | currentdir = os.path.dirname(os.path.realpath(__file__)) 20 | parentdir = os.path.dirname(currentdir) 21 | sys.path.append(parentdir) 22 | from plot.multi_plot import multiPlot 23 | import plot.colors as co 24 | from points.folder_points import folderPoints 25 | 26 | # plotting 27 | matplotlib.rcParams['svg.fonttype'] = 'none' 28 | matplotlib.rc('font', family='Arial') 29 | matplotlib.rc('font', size='10.0') 30 | 31 | # logging 32 | logger = logging.getLogger(__name__) 33 | logger.setLevel(logging.DEBUG) 34 | for s in ['matplotlib', 'imageio', 'IPython', 'PIL']: 35 | logging.getLogger(s).setLevel(logging.WARNING) 36 | 37 | 38 | #------------------------------------------- 39 | 40 | class tracePlot(multiPlot): 41 | '''for plotting yvar vs. x position at different times or different spacings, for various folders''' 42 | 43 | def __init__(self, topFolder:Union[List[str], str], exportFolder:str, yvar:str, cvar:str, xunits:str='niw', **kwargs): 44 | if cvar=='time': 45 | cc = 'const' 46 | else: 47 | cc = cvar 48 | self.cvarreal = cvar 49 | super().__init__(topFolder, exportFolder, 'xbehind', yvar, cvar=cc, **kwargs) # feed initialization dummy variables because xvar and yvar might not be folderStats attributes 50 | self.units = {} 51 | self.cvar = cvar 52 | if cvar=='time': 53 | self.colors = co.plotColors([round(0.1*x,2) for x in range(26)], 'time', 'time (s)', **kwargs) 54 | self.legend.colors = self.colors 55 | self.xunits = xunits 56 | self.plot() 57 | self.clean() 58 | if self.export: 59 | self.exportIm(**kwargs) 60 | 61 | 62 | def getFileLabel(self) -> str: 63 | '''get a label for exporting file names''' 64 | self.label = f'trace_{self.cvarreal}_{self.yvarreal}' 65 | 66 | def xbStr(self) -> str: 67 | '''convert xbehind to a readable string''' 68 | if type(self.xbehind) is dict: 69 | xbstr ='' 70 | for key,val in self.xbehind.items(): 71 | xbstr = f'{xbstr}{os.path.basename(key)}{val}_' 72 | else: 73 | xbstr = str(self.xbehind) 74 | return xbstr 75 | 76 | 77 | def plotFolderAx(self, ax, row:pd.Series) -> None: 78 | '''plot all lines for one folder on the axis''' 79 | fs = self.fstats[row['folder']] 80 | fp = folderPoints(fs) 81 | df, u = fp.importSummary() 82 | if len(df)==0: 83 | return 84 | df = df[(df.xbehind>-2)&(df.xbehind<6)] # throw out the areas too close to the boundaries 85 | if self.xunits=='niw' and self.xvarreal in ['x', 'xbehind']: 86 | df, u = fp.convertXunits(df, u, self.xunits, xvar=self.xvarreal) 87 | self.units = {**self.units, **u} 88 | 89 | if not self.cvar=='time': 90 | df = df[df.time==2.5] # just select the slices at 2.5 seconds 91 | 92 | if self.cvar in df: 93 | for val in df[self.cvar].unique(): 94 | color = self.colors.getColor(val) 95 | df1 = df[df[self.cvar]==val] 96 | ax.plot(df1[self.xvarreal], df1[self.yvarreal], color=color, marker=None, lw=0.75) 97 | else: 98 | color = self.colors.getColor(row['cvar']) 99 | ax.plot(df[self.xvarreal], df[self.yvarreal], color=color, marker=None, lw=0.75) 100 | 101 | def plotAx(self, row:int, col:int): 102 | '''plot values on the axis''' 103 | ax = self.axs[row][col] 104 | folders = self.selectFiles(row,col) 105 | for i,row in folders.iterrows(): 106 | self.plotFolderAx(ax, row) 107 | self.addIdeal(ax, self.yvarreal) 108 | ax.axvline(0, ls='--', c='gray', lw=0.75) # add a line at the nozzle 109 | 110 | 111 | def plot(self): 112 | '''plot the xvar and yvar on each axis''' 113 | for row in range(self.nrow): 114 | for col in range(self.ncol): 115 | self.plotAx(row, col) 116 | if self.cvar=='time': 117 | self.legend.colorBar(self.fig) -------------------------------------------------------------------------------- /py/plot/txt_plot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for plotting names of simulations for easy referencing''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | from typing import List, Dict, Tuple, Union, Any, TextIO 8 | import logging 9 | import traceback 10 | 11 | # local packages 12 | currentdir = os.path.dirname(os.path.realpath(__file__)) 13 | parentdir = os.path.dirname(currentdir) 14 | sys.path.append(parentdir) 15 | from plot.meta_plot import metaPlot 16 | from folder_stats import folderStats 17 | 18 | # logging 19 | logger = logging.getLogger(__name__) 20 | logger.setLevel(logging.DEBUG) 21 | for s in ['matplotlib', 'imageio', 'IPython', 'PIL']: 22 | logging.getLogger(s).setLevel(logging.WARNING) 23 | 24 | 25 | #------------------------------------------- 26 | 27 | class txtPlot(metaPlot): 28 | '''plot the simulation folder basenames 29 | topFolder is the folder that holds all the files 30 | overwrite True to overwrite plots 31 | ''' 32 | 33 | def __init__(self, topFolder:str 34 | , exportFolder:str 35 | , xr:List[float] = [-0.5, 0.5] 36 | , yr:List[float] = [-0.9, 0.9] 37 | , **kwargs): 38 | super().__init__(topFolder, exportFolder=exportFolder, xr=xr, yr=yr, **kwargs) 39 | 40 | def getFileLabel(self) -> str: 41 | '''get a label for exporting file names''' 42 | self.label = f'name' 43 | 44 | def metaItem(self, fs:folderStats) -> str: 45 | '''get the text to plot from the folderStats object. holder text, replace this for subclasses''' 46 | return fs.bn 47 | -------------------------------------------------------------------------------- /py/points/points_tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Collecting points from folders''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import csv 8 | import numpy as np 9 | import pandas as pd 10 | from shapely.geometry import Polygon 11 | import re 12 | from typing import List, Dict, Tuple, Union, Any 13 | import logging 14 | import traceback 15 | 16 | # local packages 17 | currentdir = os.path.dirname(os.path.realpath(__file__)) 18 | parentdir = os.path.dirname(currentdir) 19 | sys.path.append(parentdir) 20 | import file.plainIm as pi 21 | import tools.strings as st 22 | from folder_stats import geoStats, folderStats 23 | 24 | # logging 25 | logger = logging.getLogger(__name__) 26 | logger.setLevel(logging.DEBUG) 27 | 28 | 29 | 30 | ################################################################# 31 | 32 | def xpts(data:pd.DataFrame) -> pd.Series: 33 | '''List of all of the x positions in the dataframe''' 34 | xl = np.sort(data.x.unique()) 35 | return xl 36 | 37 | def closest(lst:List[float], K:float) -> float: # RG 38 | '''find the closest value in a list to the float K''' 39 | lst = np.asarray(lst) 40 | idx = (np.abs(lst - K)).argmin() 41 | return lst[idx] 42 | 43 | 44 | def pdCentroid(xs:pd.DataFrame) -> Tuple[float, float, float]: 45 | '''find the centroid of a slice represented as a pandas dataframe''' 46 | return centroid(np.array(xs[['y', 'z']])) 47 | 48 | def setX(pts2d:np.array, x:float) -> np.array: # RG 49 | '''given an array of 2d points pts2d, create an array of 3d points at position x''' 50 | out = np.zeros([len(pts2d), 3]) 51 | out[:, 1:3] = pts2d 52 | out[:, 0] = x 53 | return out 54 | 55 | 56 | def setZ(pts2d:np.array, z:float) -> np.array: 57 | '''given an array of 2d points pts2d, create an array of 3d points at position z''' 58 | out = np.zeros([len(pts2d), 3]) 59 | out[:, 0:2] = pts2d 60 | out[:, 2] = z 61 | return out 62 | 63 | def sortPolar(pts:np.array) -> List[float]: # RG 64 | '''sort a list of points from 0 to 2 pi 65 | pts is a 3d list of unarranged points lying in the x, y, or z plane''' 66 | # maps x,y,z so z is the plane the xs lies in 67 | for i in range(3): 68 | if np.all(pts[:,i] == pts[0,i]): 69 | x = pts[:,(i+1)%3] 70 | y = pts[:,(i+2)%3] 71 | z = pts[:,i] 72 | j = i 73 | # organizes points by polar coordinates with the center as the origin 74 | x0 = np.mean(x) 75 | y0 = np.mean(y) 76 | r = np.sqrt((x-x0)**2+(y-y0)**2) 77 | theta = np.where(y>y0, np.arccos((x-x0)/r), 2*np.pi-np.arccos((x-x0)/r)) 78 | mask = np.argsort(theta) 79 | xsort = x[mask] 80 | ysort = y[mask] 81 | xsort = np.append(xsort,xsort[0]) 82 | ysort = np.append(ysort,ysort[0]) 83 | z = np.append(z,z[0]) 84 | # maps x,y,z back to original 85 | ptstemp = np.asarray(list(zip(xsort,ysort,z))) 86 | ptssort = np.zeros((len(xsort),3)) 87 | for k in range(3): 88 | ptssort[:,(j+k+1)%3]=ptstemp[:,k] 89 | return ptssort, theta 90 | 91 | def denoise(pts:np.ndarray) -> np.ndarray: # RG 92 | '''takes the interface which has inner and outer points, making jagged edges, 93 | and roughly turns it into only outer points 94 | outputs the outer points''' 95 | x = pts[:,0] 96 | y = pts[:,1] 97 | x0 = np.mean(x) 98 | y0 = np.mean(y) 99 | points = np.zeros((90,2)) 100 | phi = np.degrees(sortPolar(setZ(np.column_stack((x,y)),0))[1])/4 # quarter-angle values for each point 101 | miss = 0 # handles situations where there are no points in the slice 102 | for theta in range(90): # slice for effectively every 4 degrees 103 | eligx = [] 104 | eligy = [] 105 | r = [] 106 | for i,val in enumerate(phi): 107 | if val>=theta and val0: 112 | d = r.index(max(r)) 113 | outpt = [eligx[d],eligy[d]] # coordinates of point furthest away 114 | points[theta-miss] = outpt # add point to points 115 | else: 116 | miss+=1 117 | if miss==0: 118 | return points 119 | return points[:-miss] 120 | 121 | def xspoints(p:pd.DataFrame, dist:float, ore:str) -> Union[np.ndarray, List[float]]: # RG 122 | '''take interface points and shift them the desired offset from the nozzle 123 | outputs the points and the centerpoint of the points''' 124 | y = p['y'] 125 | z = p['z'] 126 | d = dist 127 | if ore=='y': 128 | y = [a-d for a in y] # shift cross section by d 129 | elif ore=='z': 130 | z = [a-d for a in z] 131 | else: 132 | raise Exception('Valid orientations are y and z') 133 | vertices = list(zip(y,z)) # list of tuples 134 | points = np.asarray(vertices) # numpy array of coordinate pairs 135 | centery = np.mean(y) 136 | centerz = np.mean(z) 137 | center = [centery, centerz] 138 | points = denoise(points) 139 | return points, center -------------------------------------------------------------------------------- /py/scrape_tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for generating legends for OpenFOAM simulations of embedded 3D printing of single filaments. Written for OpenFOAM v1912 and OpenFOAM 8. Scrapes input files for input variables.''' 3 | 4 | # external packages 5 | import os,sys 6 | from typing import List, Dict, Tuple, Union, Any, TextIO 7 | import logging 8 | import re 9 | 10 | 11 | # local packages 12 | currentdir = os.path.dirname(os.path.realpath(__file__)) 13 | parentdir = os.path.dirname(currentdir) 14 | sys.path.append(parentdir) 15 | 16 | # logging 17 | logger = logging.getLogger(__name__) 18 | logger.setLevel(logging.DEBUG) 19 | for s in ['matplotlib']: 20 | logging.getLogger(s).setLevel(logging.WARNING) 21 | 22 | 23 | 24 | #------------------------------------------------------------------------------------------------- 25 | 26 | def ca(col:List[List[str]], hlist:List[str], li:List[List[str]]) -> None: 27 | '''ca is used by scrape.table() to compile all of the data in the object into a table 28 | col is a table with 2 columns. 29 | hlist is a list of headers that describe this chunk of data. they are added as rows with an empty value 30 | li is a list of [variable name, value] to add to col''' 31 | for i in hlist: 32 | col.append([i, '']) 33 | for i in li: 34 | col.append(i) 35 | 36 | def placeInList(l:List[List[str]], s:str, v:str) -> None: 37 | '''placeInList puts a value v into a list with 2 columns, where s is the name of the variable 38 | this function will only place the value into the list if the variable name s is in the list and there isn't already a value there 39 | l is a list 40 | s is a string 41 | v is a value''' 42 | 43 | # find the index in the list where this value should go 44 | i = -1 45 | found=False 46 | while i0 and l[i][0]==s: 49 | found=True 50 | 51 | # put the value in the list 52 | if not found: 53 | return 54 | else: 55 | l[i][1] = v # this puts the value in the list 56 | return 57 | 58 | 59 | def cancelUnits(s:str) -> str: 60 | '''cancelUnits removes the units list (e.g. [ 0 2 -1 0 0 0 0]) from a value 61 | s is a string''' 62 | strs = re.split(' ', s) 63 | return strs[-1] 64 | 65 | 66 | def listLevel(startString:str, endString:str, line:str, f:TextIO, l:List[List[str]]) -> str: 67 | '''listLevel is a tool that looks for sections of interest within files and scrapes values out of them 68 | startString is a string that tells us that we've reached the section of interest. It should be at the beginning of the line 69 | endString tells us that we've reached the end of the section of interest. It should be at the beginning of the line. 70 | line is the starting line 71 | f is a file stream created by open() 72 | l is a list of variable names and values that we're going to scrape values into 73 | returns the line we just read''' 74 | 75 | while not line.startswith(startString): 76 | line = f.readline() 77 | 78 | while not line.startswith(endString): 79 | strs = re.split(';|\t', line) # split the line at tabs and semicolons 80 | ii = 0 81 | si = 0 82 | while ii str: 93 | '''readLevel0 is a simpler version of listLevel, where instead of placing many values in a list, 94 | we're looking for a single value. This function targets lines in files that have no tabs at the beginning, just "name\tvalue" 95 | s is a trigger string that tells us we've found the value we're looking for 96 | line is a starting line 97 | f is a file stream created by open() 98 | obj is a [1x2] list into which we'll store our value 99 | returns the line we just read''' 100 | while not line.startswith(s): 101 | line = f.readline() 102 | strs = re.split(';|\t', line) # split the line at ; and tabs 103 | obj[1] = strs[1] # the value will always be the second item 104 | return line -------------------------------------------------------------------------------- /py/summarize/ideals.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Ideal values of measured variables''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import csv 8 | import numpy as np 9 | import pandas as pd 10 | from shapely.geometry import Polygon 11 | import re 12 | from typing import List, Dict, Tuple, Union, Any 13 | import logging 14 | import traceback 15 | 16 | # local packages 17 | currentdir = os.path.dirname(os.path.realpath(__file__)) 18 | parentdir = os.path.dirname(currentdir) 19 | sys.path.append(parentdir) 20 | from points.slice_points import slicePoints 21 | from summarize.summarizer import summarizer 22 | 23 | 24 | # logging 25 | logger = logging.getLogger(__name__) 26 | logger.setLevel(logging.DEBUG) 27 | 28 | 29 | #------------------------------------------------------ 30 | 31 | class ideals: 32 | '''stores the ideal values for measurements''' 33 | 34 | def __init__(self): 35 | self.adj = {'centeryr':0, 'centerzr':0 36 | , 'centeryn':0, 'centerzn':0 37 | , 'arean':1 38 | , 'maxheightn':1, 'maxwidthn':1 39 | , 'vertdisp':0, 'vertdispn':0 40 | , 'horizdisp':0, 'horizdispn':0 41 | , 'aspectration':1, 'speeddecay':1 42 | , 'roughness':0, 'emptiness':0 43 | , 'asymmetryh':0, 'asymmetryv':0} 44 | self.single = {'centery':0, 'centerz':0 45 | , 'centeryn':0, 'centerzn':0 46 | , 'arean':1 47 | , 'maxheightn':1, 'maxwidthn':1 48 | , 'vertdisp':0, 'vertdispn':0 49 | , 'aspectratio':1, 'speeddecay':1} 50 | 51 | def getValue(self, var:str, printType:str=''): 52 | if printType=='adjacent': 53 | if var in self.adj: 54 | return self.adj[var] 55 | else: 56 | return '' 57 | elif printType=='single': 58 | if var in self.single: 59 | return self.single[var] 60 | else: 61 | return '' 62 | else: 63 | if var in self.adj: 64 | return self.adj[var] 65 | elif var in self.single: 66 | return self.single[var] 67 | else: 68 | return '' 69 | -------------------------------------------------------------------------------- /py/summarize/legend_summary.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for generating legends for OpenFOAM simulations of embedded 3D printing of single filaments. Written for OpenFOAM v1912 and OpenFOAM 8. Scrapes input files for input variables.''' 3 | 4 | # external packages 5 | import os 6 | import pandas as pd 7 | from typing import List, Dict, Tuple, Union, Any, TextIO 8 | import logging, platform, socket, sys 9 | 10 | # local packages 11 | currentdir = os.path.dirname(os.path.realpath(__file__)) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.append(parentdir) 14 | import file.file_handling as fh 15 | import folder_scraper as fs 16 | 17 | # logging 18 | logger = logging.getLogger(__name__) 19 | logger.setLevel(logging.DEBUG) 20 | for s in ['matplotlib']: 21 | logging.getLogger(s).setLevel(logging.WARNING) 22 | 23 | 24 | 25 | #------------------------------------------------------------------------------------------------- 26 | 27 | def legendSummary(topFolders:str, exportFN:str) -> None: 28 | '''scrape all legends into one table''' 29 | o = [] 30 | for topfolder in topFolders: 31 | for f in fh.simFolders(topfolder): 32 | l = fs.legendUnique(f) 33 | if len(l)>0: 34 | o.append(l) 35 | p = pd.DataFrame(o) 36 | p.to_csv(exportFN) 37 | print(f'Exported {exportFN}') -------------------------------------------------------------------------------- /py/summarize/log_reader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for plotting convergence for OpenFOAM simulations of embedded 3D printing of single filaments. Written for OpenFOAM v1912 and OpenFOAM 8. Scrapes input files for input variables.''' 3 | 4 | # external packages 5 | import numpy as np 6 | import os 7 | import re 8 | import pandas as pd 9 | import csv 10 | from typing import List, Dict, Tuple, Union, Any, TextIO 11 | import logging, platform, socket, sys 12 | 13 | # local packages 14 | currentdir = os.path.dirname(os.path.realpath(__file__)) 15 | parentdir = os.path.dirname(currentdir) 16 | sys.path.append(parentdir) 17 | from file.file_handling import folderHandler 18 | from file.plainIm import plainIm, plainExp 19 | 20 | # logging 21 | logger = logging.getLogger(__name__) 22 | logger.setLevel(logging.DEBUG) 23 | for s in ['matplotlib', 'imageio', 'IPython', 'PIL']: 24 | logging.getLogger(s).setLevel(logging.WARNING) 25 | 26 | 27 | #------------------------------------------------------------------------------------------------- 28 | 29 | class logReader: 30 | '''scrapes info from logs''' 31 | 32 | def __init__(self, folder:str, overwrite:bool=False) -> None: 33 | self.folder = folder 34 | self.fh = folderHandler(folder) 35 | self.importFile(overwrite=overwrite) 36 | 37 | def fn(self): 38 | '''get the name of the interfoam file''' 39 | cf = self.fh.caseFolder() 40 | fn = os.path.join(cf, 'log_interFoam') 41 | if not os.path.exists(fn): 42 | fn = os.path.join(os.path.dirname(cf), 'log_interFoam') 43 | return fn 44 | 45 | def exportFN(self): 46 | '''get the name of the file to export to''' 47 | cf = self.fh.caseFolder() 48 | return os.path.join(cf, 'log_read.csv') 49 | 50 | def importFile(self, overwrite:bool=False) -> int: 51 | '''import values to the dataframe''' 52 | fn = self.exportFN() 53 | if os.path.exists(fn) and not overwrite: 54 | self.df,self.u = plainIm(fn, ic=0) 55 | else: 56 | self.readLog() 57 | self.exportFile() 58 | 59 | def exportFile(self): 60 | '''export values to a csv''' 61 | plainExp(self.exportFN(), self.df, self.u) 62 | 63 | def logEntry(self) -> dict: 64 | '''the logEntry dictionary is used to store information scraped from log files''' 65 | return {'courantmin': 0, 'courantmax': 0, 'deltaT': 0, 'simTime': 0, 'ralpha': 0, 'rprgh': 0, 'realtime': 0, 'cells': 0} 66 | 67 | def selectIf(self, strs:List[str], i:int) -> float: 68 | '''Get the float out of the list of strings, if the list of strings is long enough. otherwise, raise an error''' 69 | if len(strs)>i: 70 | try: 71 | f = float(strs[i]) 72 | except Exception as e: 73 | print(e) 74 | raise NameError 75 | else: 76 | return f 77 | else: 78 | raise NameError 79 | 80 | def readLog(self): 81 | '''scrape values from the log''' 82 | file = self.fn() 83 | if not os.path.exists(file): 84 | return 85 | li = [] 86 | with open(file, 'r') as f: 87 | for i in range(50): # skip all the headers and startup output 88 | line = f.readline() 89 | while line: 90 | try: 91 | if line.startswith('Courant'): # we've hit a new time step 92 | newEntry = self.logEntry() 93 | # li.append(logEntry()) # create a new object and store it in the list 94 | # lectr+=1 95 | if len(li)>0: 96 | newEntry['cells'] = li[-1]['cells'] 97 | # copy the number of cells from the last step and only adjust if the log says the number changed 98 | strs = re.split('Courant Number mean: | max: |\n', line) 99 | newEntry['courantmin'] = self.selectIf(strs, 1) 100 | newEntry['courantmax'] = self.selectIf(strs, 2) 101 | elif line.startswith('deltaT'): 102 | strs = re.split('deltaT = |\n', line) 103 | newEntry['deltaT'] = self.selectIf(strs, 1) 104 | elif line.startswith('Time = '): 105 | strs = re.split('Time = |\n', line) 106 | newEntry['simTime'] = self.selectIf(strs, 1) 107 | if len(li)>0: 108 | lastTime = li[-1]['simTime'] 109 | while lastTime>newEntry['simTime'] and len(li)>1: 110 | li = li[:-1] 111 | lastTime = li[-1]['simTime'] 112 | elif line.startswith('Unrefined from '): 113 | strs = re.split('Unrefined from | to | cells.\n', line) 114 | newEntry['cells'] = self.selectIf(strs, 2) 115 | if len(li)>0 and li[-1]['cells']==0: 116 | for i in range(len(li)): 117 | li[i]['cells'] = float(strs[1]) 118 | # the log never says the initial number of cells, 119 | # but it says the previous number if it changes the number of cells, 120 | # so to get the initial value, look for the first time the mesh is refined 121 | elif line.startswith('smoothSolver'): 122 | strs = re.split('Final residual = |, No Iterations', line) 123 | newEntry['ralpha'] = self.selectIf(strs, 1) 124 | elif line.startswith('DICPCG: Solving for p_rgh,'): 125 | strs = re.split('Final residual = |, No Iterations', line) 126 | rprgh = self.selectIf(strs, 1) 127 | newEntry['rprgh'] = rprgh 128 | elif line.startswith('ExecutionTime'): 129 | strs = re.split('ExecutionTime = | s', line) 130 | newEntry['realtime'] = self.selectIf(strs, 1) 131 | if newEntry['ralpha']>0 and newEntry['rprgh']>0: 132 | while len(li)>0 and newEntry['simTime'] None: 61 | '''for a single slice in time or position, find the point where it has flattened out''' 62 | l1 = l1.sort_values(by=self.other) # sort the slice by time if mode is x, x if mode is time 63 | list2 = l1[self.other].unique() 64 | vdrange = 100 65 | i = -1 66 | while vdrange>self.vdcrit and i+1=xtother-self.dother/2) & (l1[self.other]<=xtother+self.dother/2)] 70 | # get a chunk of size dt centered around this time 71 | vdrange = l2[self.col].max()-l2[self.col].min() 72 | if i+1=xtother-self.dother/2) & (l1[self.other]<=xtother+self.dother/2)] 78 | # get a chunk of size dt centered around this time 79 | vdrange = l2[self.col].max()-l2[self.col].min() 80 | if i+1 pd.DataFrame: 101 | '''go through all the slices and find the range where the values are steady''' 102 | 103 | sfn = os.path.join(self.folder, 'sliceSummaries.csv') 104 | if not os.path.exists(sfn): 105 | return 106 | ss, ssunits = plainIm(sfn) 107 | 108 | self.flatlist = [] 109 | if self.mode=='xbehind': # mode is the variable that we use to split into groups 110 | self.other='time' # other is the variable that we scan across 111 | else: 112 | self.other='xbehind' 113 | 114 | self.o0 = self.other[0] 115 | # copy units from the summary table 116 | self.units = {self.var0():ssunits[self.mode], self.var1():ssunits[self.other], self.var2():ssunits[self.other]} 117 | 118 | if len(ss)<2: 119 | return pd.DataFrame([], columns=self.units.keys()) 120 | 121 | slicevals = ss[self.mode].unique() # this gets the list of unique values for the mode variable 122 | for sliceval in slicevals: 123 | self.findSliceVal(ss[ss[self.mode]==sliceval], sliceval) 124 | self.df = pd.DataFrame(self.flatlist) 125 | 126 | def export(self) -> None: 127 | '''export the file''' 128 | if len(self.df)>0: 129 | plainExp(self.fn, self.df, self.units) 130 | -------------------------------------------------------------------------------- /py/summarize/sum_and_steady.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Analyzing simulated single filaments''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import csv 8 | import numpy as np 9 | import pandas as pd 10 | from shapely.geometry import Polygon 11 | import re 12 | from typing import List, Dict, Tuple, Union, Any 13 | import logging 14 | import traceback 15 | 16 | # local packages 17 | currentdir = os.path.dirname(os.path.realpath(__file__)) 18 | parentdir = os.path.dirname(currentdir) 19 | sys.path.append(parentdir) 20 | from summarize.steady import steadyMetrics 21 | from summarize.summarizer_single import summarizerSingle 22 | from summarize.summarizer_adjacent import summarizerAdjacent 23 | 24 | 25 | # logging 26 | logger = logging.getLogger(__name__) 27 | logger.setLevel(logging.DEBUG) 28 | 29 | #------------------------------------------- 30 | 31 | class sumAndSteadySingle: 32 | '''for a single line, summarize and get steady metrics''' 33 | 34 | def __init__(self, folder:str, overwrite:bool=False): 35 | self.sa = summarizerSingle(folder, overwrite=overwrite) 36 | if self.sa.success: 37 | for s in ['time', 'xbehind']: 38 | steadyMetrics(folder, overwrite=overwrite, mode=s, dother=1, vdcrit=0.01, col='vertdispn') 39 | 40 | class sumAndSteadyAdjacent: 41 | '''for 2 adjacent lines, summarize and get steady metrics''' 42 | 43 | def __init__(self, folder:str, overwrite:bool=False): 44 | print(folder) 45 | self.sa = summarizerAdjacent(folder, overwrite=overwrite) 46 | if self.sa.success: 47 | for s in ['time', 'xbehind']: 48 | steadyMetrics(folder, overwrite=overwrite, mode=s, dother=1, vdcrit=0.01, col='vertdispn') 49 | 50 | -------------------------------------------------------------------------------- /py/summarize/summarizer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Analyzing simulated single filaments''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import csv 8 | import numpy as np 9 | import pandas as pd 10 | import re 11 | from typing import List, Dict, Tuple, Union, Any 12 | import logging 13 | import traceback 14 | 15 | # local packages 16 | currentdir = os.path.dirname(os.path.realpath(__file__)) 17 | parentdir = os.path.dirname(currentdir) 18 | sys.path.append(parentdir) 19 | from points.folder_points import folderPoints 20 | from folder_stats import folderStats 21 | from file.file_handling import folderHandler 22 | from plainIm import * 23 | 24 | 25 | # logging 26 | logger = logging.getLogger(__name__) 27 | logger.setLevel(logging.DEBUG) 28 | 29 | 30 | #------------------------------------------------------ 31 | 32 | class summarizer: 33 | '''given a simulation, get critical statistics on the filament shape 34 | folder is the full path name to the folder holding all the files for the simulation 35 | there must be an interfacePoints folder holding csvs of the interface points 36 | overwrite true to overwrite existing files, false to only write new files 37 | a return value of 0 indicates success 38 | a return value of 1 indicates failure''' 39 | 40 | def __init__(self, folder:str, overwrite:bool=False): 41 | self.folder = folder 42 | self.success = False 43 | if not os.path.exists(folder): 44 | return 45 | self.fn = self.ssFile() 46 | if os.path.exists(self.fn) and not overwrite: 47 | self.success = True 48 | return 49 | self.fh = folderHandler(self.folder) 50 | self.cf = self.fh.caseFolder() 51 | if not os.path.exists(self.cf): 52 | return 53 | 54 | self.fs = folderStats(self.folder) 55 | self.fp = folderPoints(self.fs) 56 | self.summarize() 57 | 58 | def importSS(self) -> Tuple[pd.DataFrame, dict]: 59 | '''import slice summaries. folder is full path name''' 60 | self.df, self.units = plainIm(self.fn, 0) 61 | if len(d)==0: 62 | return [], [] 63 | try: 64 | for s in self.df: 65 | self.df[s] = pd.to_numeric(self.df[s], errors='coerce') # remove non-numeric times 66 | except Exception as e: 67 | pass 68 | self.df = self.df.dropna() # drop NA values 69 | 70 | return self.df, self.units 71 | 72 | def ssFile(self) -> str: 73 | '''slice Summaries file name''' 74 | return os.path.join(self.folder, 'sliceSummaries.csv') 75 | 76 | def addFile(self, f:str) -> None: 77 | '''summarize the file and add it to the stack''' 78 | print(f) 79 | data, self.units = self.fp.importPointsFile(os.path.join(self.ipfolder, f)) 80 | if len(data)==0: 81 | return 82 | xlist = self.xlist(data) 83 | for x in xlist: 84 | sli = data[data['x']==x] 85 | if len(sli)>9: 86 | ss1 = self.sliceSummary(sli) 87 | self.s.append(ss1) 88 | 89 | def getTable(self) -> pd.DataFrame: 90 | '''go through all the interface points files and summarize each x and time slice 91 | fs should be a folderStats object 92 | outputs a pandas DataFrame''' 93 | self.ipfolder = os.path.join(self.folder, 'interfacePoints') 94 | if not os.path.exists(self.ipfolder): 95 | raise ValueError('No interface points') 96 | ipfiles = os.listdir(self.ipfolder) 97 | if len(ipfiles)==0: 98 | logging.info(f'No slices recorded in {self.folder}') 99 | header = self.defaultHeader() 100 | self.df = pd.DataFrame([], columns=header) 101 | self.units = {} 102 | return 103 | self.s = [] 104 | 105 | for f in ipfiles: 106 | self.addFile(f) 107 | 108 | self.df = pd.DataFrame(self.s, dtype=np.float64) 109 | self.df.dropna(inplace=True) 110 | 111 | def saveSummary(self): 112 | '''save the summary points''' 113 | if not hasattr(self, 'df'): 114 | return 115 | self.df.sort_values(by=['time', 'x'], inplace=True) 116 | plainExp(self.fn, self.df, self.sliceUnits()) 117 | 118 | def xlist(self, data) -> list: 119 | '''get a list of x positions to probe''' 120 | xlist = np.sort(data.x.unique()) 121 | return xlist 122 | 123 | 124 | def summarize(self) -> None: 125 | '''summarize the slices''' 126 | 127 | # summarizeSlices will raise an exception if there are no interface 128 | # points files 129 | self.getTable() 130 | 131 | # if there are points in the summary, save them 132 | if len(self.df)>0: 133 | self.saveSummary() 134 | self.success = True 135 | -------------------------------------------------------------------------------- /py/summarize/summarizer_adjacent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Analyzing simulated single filaments''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import csv 8 | import numpy as np 9 | import pandas as pd 10 | from shapely.geometry import Polygon 11 | import re 12 | from typing import List, Dict, Tuple, Union, Any 13 | import logging 14 | import traceback 15 | 16 | # local packages 17 | currentdir = os.path.dirname(os.path.realpath(__file__)) 18 | parentdir = os.path.dirname(currentdir) 19 | sys.path.append(parentdir) 20 | from points.slice_points import slicePoints 21 | from summarize.summarizer import summarizer 22 | 23 | 24 | # logging 25 | logger = logging.getLogger(__name__) 26 | logger.setLevel(logging.DEBUG) 27 | 28 | 29 | #------------------------------------------------------ 30 | 31 | class summarizerAdjacent(summarizer): 32 | '''for adjacent lines for a single simulation''' 33 | 34 | def __init__(self, folder:str, overwrite:bool=False): 35 | super().__init__(folder, overwrite) 36 | 37 | def defaultHeader(self) -> list: 38 | return list(self.sliceUnits().keys()) 39 | 40 | def sliceUnits(self) -> dict: 41 | '''Find the units for a slice summary dictionary based on the units of the interfacePoints csv''' 42 | if not hasattr(self, 'units'): 43 | xu = 'mm' 44 | tu = 's' 45 | su = 'mm/s' 46 | else: 47 | xu = self.units['x'] 48 | tu = self.units['time'] 49 | su = self.units['vx'] 50 | return {'x':xu, 'xbehind':xu, 'time':tu, 'centery':xu, 'centerz':xu, 'centeryr':xu, 'centerzr':xu 51 | , 'area':xu+'^2', 'maxheight':xu, 'maxwidth':xu, 'centeryn':'', 'centerzn':'' 52 | , 'arean':'', 'maxheightn':'', 'maxwidthn':'', 'vertdisp':xu, 'vertdispn':'' 53 | , 'horizdisp':xu, 'horizdispn':'' 54 | , 'aspectratio':'', 'aspectration':'', 'speed':su, 'speeddecay':'' 55 | , 'roughness':'', 'emptiness':'', 'asymmetryh':'w', 'asymmetryv':'h'} 56 | 57 | 58 | 59 | def sliceSummary(self, sli:pd.DataFrame) -> Dict[str, float]: 60 | '''sliceSummary collects important stats from a slice of a filament at a certain x and time and returns as a dictionary 61 | sli is a subset of points as a pandas dataframe 62 | fs is a folderStats object''' 63 | ev = -100 # error value 64 | rv = dict([[x, ev] for x in self.defaultHeader()]) 65 | # time is in s 66 | # centery, centerz, minz, vertdisp, maxz, maxheight, maxwidth are in mm 67 | # speed is in mm/s 68 | # centeryn, centerzn, vertdispn, maxheightn, maxwidthn are normalized by nozzle inner diameter 69 | # aspectratio, topbotratio are dimensionless 70 | # speeddecay is normalized by the bath speed 71 | rv['x'] = sli.iloc[0]['x'] 72 | rv['xbehind'] = rv['x']-self.fs.geo.ncx 73 | rv['time'] = np.float64(sli.iloc[0]['time']) 74 | 75 | if len(sli)<10: 76 | #logging.error('Not enough points') 77 | raise ValueError('Not enough points') 78 | 79 | sm = slicePoints(sli) 80 | rv['centery'], rv['centerz'], rv['area'] = sm.centroidAndArea() 81 | rv['centeryr'] = rv['centery'] - self.fs.geo.intentycenter 82 | rv['centerzr'] = rv['centerz'] - self.fs.geo.intentzcenter 83 | rv['maxheight'] = sli['z'].max()-sli['z'].min() 84 | rv['maxwidth'] = sli['y'].max()-sli['y'].min() 85 | if rv['maxheight']==0 or rv['maxwidth']==0: 86 | #logging.error('Cross-section is too small') 87 | raise ValueError('Cross-section is too small') 88 | 89 | rv['centerzn'] = rv['centerzr']/self.fs.geo.niw # this is the center of mass 90 | rv['centeryn'] = rv['centeryr']/self.fs.geo.niw 91 | rv['arean'] = rv['area']/self.fs.geo.intentarea # normalize area by nozzle area 92 | rv['maxheightn'] = rv['maxheight']/self.fs.geo.intenth # normalize height by nozzle diameter 93 | rv['maxwidthn'] = rv['maxwidth']/self.fs.geo.intentw # normalized width by intended width 94 | 95 | rv['vertdisp'] = (sli['z'].min() - self.fs.geo.intentzbot) 96 | rv['vertdispn'] = rv['vertdisp']/self.fs.geo.niw 97 | rv['horizdisp'] = (sli['y'].min() - self.fs.geo.intentyleft) 98 | rv['horizdispn'] = rv['horizdisp']/self.fs.geo.niw 99 | rv['aspectratio'] = rv['maxheight']/rv['maxwidth'] 100 | rv['aspectration'] = rv['aspectratio']/(self.fs.geo.intenth/self.fs.geo.intentw) 101 | rv['speed'] = sli['vx'].mean() # speed of interface points in x 102 | rv['speeddecay'] = rv['speed']/self.fs.sup.dynamic['v'] # relative speed of interface relative to the bath speed 103 | rv['roughness'] = sm.roughness() 104 | rv['emptiness'] = sm.emptiness() 105 | rv['asymmetryh'] = 0.5-(rv['centery']-sli.y.min())/rv['maxwidth'] 106 | rv['asymmetryv'] = 0.5-(rv['centerz']-sli.z.min())/rv['maxheight'] 107 | 108 | return rv -------------------------------------------------------------------------------- /py/summarize/summarizer_single.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Analyzing simulated single filaments''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import csv 8 | import numpy as np 9 | import pandas as pd 10 | import re 11 | from typing import List, Dict, Tuple, Union, Any 12 | import logging 13 | import traceback 14 | 15 | # local packages 16 | currentdir = os.path.dirname(os.path.realpath(__file__)) 17 | parentdir = os.path.dirname(currentdir) 18 | sys.path.append(parentdir) 19 | import points.points_tools as pto 20 | from summarize.summarizer import summarizer 21 | 22 | 23 | # logging 24 | logger = logging.getLogger(__name__) 25 | logger.setLevel(logging.DEBUG) 26 | 27 | 28 | #------------------------------------------------------ 29 | 30 | class summarizerSingle(summarizer): 31 | '''for single lines''' 32 | 33 | def __init__(self, folder:str, overwrite:bool=False): 34 | super().__init__(folder, overwrite) 35 | 36 | def defaultHeader(self) -> list: 37 | return ['x', 'xbehind', 'time', 'centery', 'centerz', 'area', 'maxheight', 'maxwidth', 'centeryn', 'centerzn', 'arean', 'maxheightn', 'maxwidthn','vertdisp', 'vertdispn', 'aspectratio', 'speed', 'speeddecay'] 38 | 39 | def sliceUnits(self) -> dict: 40 | '''Find the units for a slice summary dictionary based on the units of the interfacePoints csv''' 41 | xu = self.units['x'] 42 | return {'x':xu, 'xbehind':xu, 'time':self.units['time'], \ 43 | 'centery':xu, 'centerz':xu, 'area':xu+'^2', 'maxheight':xu, 'maxwidth':xu, \ 44 | 'centeryn':'', 'centerzn':'', 'arean':'', 'maxheightn':'', 'maxwidthn':'',\ 45 | 'vertdisp':xu, 'vertdispn':'', 'aspectratio':'', 'speed':self.units['vx'], 'speeddecay':''} 46 | 47 | def xlist(self, data) -> list: 48 | '''get a list of x positions to probe''' 49 | xlist = pto.xpts(data) 50 | xlist = xlist[xlist>self.fs.geo.behind] 51 | return xlist 52 | 53 | 54 | def sliceSummary(self, sli:pd.DataFrame) -> Dict[str, float]: 55 | '''sliceSummary collects important stats from a slice of a filament at a certain x and time and returns as a dictionary 56 | sli is a subset of points as a pandas dataframe 57 | ''' 58 | ev = -100 # error value 59 | rv = {'x':ev, 'xbehind':ev, 'time':ev, \ 60 | 'centery':ev, 'centerz':ev, 'area':ev, 'maxheight':ev, 'maxwidth':ev, \ 61 | 'centeryn':ev, 'centerzn':ev, 'arean':ev, 'maxheightn':ev, 'maxwidthn':ev,\ 62 | 'vertdisp':ev, 'vertdispn':ev, 'aspectratio':ev, 'speed':ev, 'speeddecay':ev} # return value 63 | # x is in mm 64 | # time is in s 65 | # centery, centerz, minz, vertdisp, maxz, maxheight, maxwidth are in mm 66 | # speed is in mm/s 67 | # centeryn, centerzn, vertdispn, maxheightn, maxwidthn are normalized by nozzle inner diameter 68 | # aspectratio, topbotratio are dimensionless 69 | # speeddecay is normalized by the bath speed 70 | rv['x'] = sli.iloc[0]['x'] 71 | rv['xbehind'] = rv['x']-self.fs.geo.ncx 72 | rv['time'] = np.float64(sli.iloc[0]['time']) 73 | 74 | if len(sli)<10: 75 | #logging.error('Not enough points') 76 | raise ValueError('Not enough points') 77 | 78 | 79 | sm = pto.shapeMeasure(sli) 80 | try: 81 | rv['centery'], rv['centerz'], rv['area'] = sm.centroidAndArea() 82 | except KeyboardInterrupt as e: 83 | raise e 84 | except: 85 | #logging.error('centroid error') 86 | raise ValueError('centroid error') 87 | rv['maxheight'] = sli['z'].max()-sli['z'].min() 88 | rv['maxwidth'] = sli['y'].max()-sli['y'].min() 89 | if rv['maxheight']==0 or rv['maxwidth']==0: 90 | #logging.error('Cross-section is too small') 91 | raise ValueError('Cross-section is too small') 92 | 93 | rv['centerzn'] = rv['centerz']/self.fs.geo.niw 94 | rv['centeryn'] = rv['centery']/self.fs.geo.niw 95 | rv['arean'] = rv['area']/(np.pi*(self.fs.geo.niw/2)**2) # normalize area by nozzle area 96 | rv['maxheightn'] = rv['maxheight']/self.fs.geo.niw # normalize height by nozzle diameter 97 | rv['maxwidthn'] = rv['maxwidth']/self.fs.geo.niw # normalized width by intended width 98 | 99 | rv['vertdisp'] = (sli['z'].min() - self.fs.geo.intentzbot) 100 | rv['vertdispn'] = rv['vertdisp']/self.fs.geo.niw 101 | rv['aspectratio'] = rv['maxheight']/rv['maxwidth'] 102 | rv['speed'] = sli['vx'].mean() # speed of interface points in x 103 | rv['speeddecay'] = rv['speed']/self.fs.geo.bv # relative speed of interface relative to the bath speed 104 | 105 | return rv -------------------------------------------------------------------------------- /py/summarize/super_summary.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Summarize slices from all folders in a single table''' 3 | 4 | # external packages 5 | import sys 6 | import os 7 | import csv 8 | import numpy as np 9 | import pandas as pd 10 | import re 11 | from typing import List, Dict, Tuple, Union, Any 12 | import logging 13 | import traceback 14 | 15 | # local packages 16 | currentdir = os.path.dirname(os.path.realpath(__file__)) 17 | parentdir = os.path.dirname(currentdir) 18 | sys.path.append(parentdir) 19 | from points.folder_points import folderPoints 20 | from folder_stats import folderStats 21 | import file.file_handling as fh 22 | from plainIm import * 23 | 24 | 25 | # logging 26 | logger = logging.getLogger(__name__) 27 | logger.setLevel(logging.DEBUG) 28 | 29 | 30 | #------------------------------------------------------ 31 | 32 | class superSummary: 33 | '''given a time and position, collect slices from all of the folders and compile them into a single summary table''' 34 | 35 | def __init__(self, topFolder:Union[List[str], str] 36 | , exportFolder:str 37 | , time:float 38 | , xbehind:float 39 | , xunits:str='mm' 40 | , **kwargs): 41 | self.topFolder = topFolder 42 | self.exportFolder = exportFolder 43 | self.time = time 44 | self.xbehind = xbehind 45 | self.xunits = xunits 46 | self.fileName() 47 | 48 | def fileName(self) -> str: 49 | '''get a label for exporting file names''' 50 | self.label = f'summary_{self.xbehind}{self.xunits}_t_{self.time}.csv' 51 | self.fn = os.path.join(self.exportFolder, self.label) 52 | 53 | def importFile(self): 54 | '''import the summary from file''' 55 | if os.path.exists(self.fn): 56 | self.df, self.units = plainIm(self.fn, 0) 57 | else: 58 | self.getTable() 59 | 60 | def addRow(self, f:str) -> None: 61 | '''add the folder to the dataframe''' 62 | fs = folderStats(f) 63 | d,u = fs.metaRow() 64 | fp = folderPoints(fs) 65 | row, u2 = fp.importSummarySlice(self.time, self.xbehind, self.xunits) 66 | if len(row)>0: 67 | d = {**d, **dict(row.iloc[0])} 68 | u = {**u, **u2} 69 | self.rowlist.append(d) 70 | self.units = {**self.units, **u} 71 | 72 | def getTable(self): 73 | '''create the table by scraping data from folders''' 74 | flist = fh.simFolders(self.topFolder) 75 | self.rowlist = [] 76 | self.units = {} 77 | for f in flist: 78 | self.addRow(f) 79 | self.df = pd.DataFrame(self.rowlist) 80 | plainExp(self.fn, self.df, self.units) 81 | -------------------------------------------------------------------------------- /py/tools/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Tools for loading settings''' 3 | 4 | # external packages 5 | import yaml 6 | import sys 7 | import os 8 | try: 9 | from box import Box 10 | except ModuleNotFoundError: 11 | nobox = True 12 | else: 13 | nobox = False 14 | import shutil 15 | 16 | 17 | #---------------------------------------------------- 18 | 19 | class Struct: 20 | def __init__(self, **entries): 21 | for key,val in entries.items(): 22 | if type(val) is dict: 23 | setattr(self, key, Struct(**val)) 24 | else: 25 | setattr(self, key, val) 26 | 27 | 28 | def getConfigDir() -> str: 29 | '''find the configs directory''' 30 | currentdir = os.path.dirname(os.path.realpath(__file__)) 31 | configdir = os.path.join(currentdir, 'configs') 32 | if not os.path.exists(configdir): 33 | parentdir = os.path.dirname(currentdir) 34 | configdir = os.path.join(parentdir, 'configs') 35 | if not os.path.exists(configdir): 36 | grandparentdir = os.path.dirname(parentdir) 37 | configdir = os.path.join(grandparentdir, 'configs') 38 | if not os.path.exists(configdir): 39 | raise FileNotFoundError(f"No configs directory found") 40 | return configdir 41 | 42 | def dumpConfigs(cfg, path:str) -> int: 43 | '''Saves config file. cfg could be a Box or a dict''' 44 | with open(path, "w") as ymlout: 45 | if type(cfg) is Box: 46 | cout = cfg.to_dict() 47 | elif type(cfg) is dict: 48 | cout = cfg 49 | else: 50 | return 1 51 | yaml.safe_dump(cout, ymlout) 52 | return 0 53 | 54 | 55 | def findConfigFile() -> str: 56 | '''find the config file and return the path''' 57 | configdir = getConfigDir() 58 | path = os.path.join(configdir,"config.yml") 59 | if os.path.exists(path): 60 | return path 61 | 62 | # config.yml does not exist: find default or template 63 | path2 = os.path.join(configdir, 'config_default.yml') 64 | if os.path.exists(path2): 65 | shutil.copy2(path2, path) 66 | return path 67 | else: 68 | path2 = os.path.join(configdir, 'config_template.yml') 69 | if os.path.exists(path2): 70 | shutil.copy2(path2, path) 71 | return path 72 | 73 | # no config_default or config_template either: find any file that looks like a config file 74 | llist = os.listdir(configdir) 75 | while not os.path.exists(path): 76 | l = llist.pop(0) 77 | if (l.endswith('yml') or l.endswith('yaml')) and ('config' in l): 78 | path = os.path.join(configdir, l) 79 | return path 80 | 81 | return path 82 | 83 | 84 | def loadConfigFile(path:str) -> Box: 85 | '''open the config file and turn it into a Box''' 86 | with open(path, "r") as ymlfile: 87 | y = yaml.safe_load(ymlfile) 88 | if nobox: 89 | cfg = Struct(**y) 90 | else: 91 | cfg = Box(y) 92 | return cfg 93 | 94 | def loadConfig() -> Box: 95 | path = findConfigFile() 96 | cfg = loadConfigFile(path) 97 | return cfg 98 | 99 | #---------------------------------------------------- 100 | 101 | cfg = loadConfig() 102 | 103 | 104 | -------------------------------------------------------------------------------- /py/tools/logs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for handling logs''' 3 | 4 | # external packages 5 | import os, sys, re, shutil, errno 6 | from typing import List, Dict, Tuple, Union, Any, TextIO 7 | from datetime import datetime 8 | import time 9 | import logging, platform, socket 10 | 11 | # local packages 12 | 13 | 14 | # logging 15 | logger = logging.getLogger(__name__) 16 | logger.setLevel(logging.DEBUG) 17 | 18 | 19 | 20 | #---------------------------------------------- 21 | 22 | def logFN(scriptFile:str) -> str: 23 | '''Get a log file name, given a script file name''' 24 | compname = socket.gethostname() 25 | base = os.path.splitext(os.path.basename(scriptFile))[0] 26 | dirpath = os.path.dirname(os.path.realpath(__file__)) 27 | try: 28 | cfgbase = cfg.path.logs 29 | except: 30 | cfgbase = 'logs' 31 | logfolder = os.path.join(dirpath, cfgbase) 32 | if not os.path.exists(logfolder): 33 | os.mkdir(logfolder) 34 | # logfolder = os.path.join(os.path.dirname(dirpath), cfgbase) 35 | # if not os.path.exists(logfolder): 36 | # logfolder = dirpath 37 | return os.path.join(logfolder,f'{base}_{compname}.log') 38 | 39 | def openLog(f:str, LOGGERDEFINED:bool, level:str="INFO", exportLog:bool=True) -> bool: 40 | '''this code lets you create log files, so you can track when you've moved files. f is the file name of the script calling the openLog function''' 41 | 42 | if not LOGGERDEFINED: 43 | loglevel = getattr(logging,level) 44 | root = logging.getLogger() 45 | if len(root.handlers)>0: 46 | # if handlers are already set up, don't set them up again 47 | return True 48 | root.setLevel(loglevel) 49 | 50 | # send messages to file 51 | if exportLog: 52 | logfile = logFN(f) 53 | filehandler = logging.FileHandler(logfile) 54 | filehandler.setLevel(loglevel) 55 | formatter = logging.Formatter("%(asctime)s/{}/%(levelname)s: %(message)s".format(socket.gethostname()), datefmt='%b%d/%H:%M:%S') 56 | filehandler.setFormatter(formatter) 57 | root.addHandler(filehandler) 58 | logging.info(f'Established log: {logfile}') 59 | 60 | # print messages 61 | handler = logging.StreamHandler(sys.stdout) 62 | handler.setLevel(loglevel) 63 | formatter2 = logging.Formatter('%(levelname)s: %(message)s') 64 | handler.setFormatter(formatter2) 65 | root.addHandler(handler) 66 | LOGGERDEFINED = True 67 | 68 | return LOGGERDEFINED 69 | 70 | def printCurrentTime() -> None: 71 | '''Print the current time''' 72 | now = datetime.now() 73 | current_time = "------ Current Time = " + now.strftime("%D, %H:%M:%S") 74 | logging.info(current_time) -------------------------------------------------------------------------------- /py/tools/val_tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Functions for importing csv''' 3 | 4 | # external packages 5 | from typing import List, Dict, Tuple, Union, Any, TextIO 6 | import logging 7 | 8 | # local packages 9 | 10 | 11 | # logging 12 | logger = logging.getLogger(__name__) 13 | logger.setLevel(logging.DEBUG) 14 | 15 | 16 | 17 | #---------------------------------------------- 18 | 19 | def tryfloat(val:Any) -> Any: 20 | try: 21 | val = float(val) 22 | except: 23 | pass 24 | if type(val) is str: 25 | return val.strip() 26 | else: 27 | return val -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.4 2 | argon2-cffi==20.1.0 3 | async-generator==1.10 4 | attrs==20.3.0 5 | backcall==0.2.0 6 | bleach==3.3.0 7 | certifi>=2022.12.07 8 | cffi==1.14.0 9 | chardet==3.0.4 10 | colorama==0.4.4 11 | cryptography==39.0.1 12 | cycler==0.10.0 13 | decorator==4.4.2 14 | defusedxml==0.6.0 15 | distlib==0.3.1 16 | entrypoints==0.3 17 | ffmpeg-python==0.2.0 18 | ffprobe-python==1.0.3 19 | file-read-backwards==2.0.0 20 | filelock==3.0.12 21 | future>=0.18.3 22 | idna==2.9 23 | imageio==2.9.0 24 | imageio-ffmpeg==0.4.2 25 | ipykernel==5.3.4 26 | ipython>=8.10 27 | ipython-genutils==0.2.0 28 | jedi==0.17.2 29 | Jinja2==2.11.3 30 | joblib>=1.2.0 31 | json5==0.9.5 32 | jsonschema==3.2.0 33 | jupyter-client==6.1.7 34 | jupyter-core>=4.11.2 35 | jupyterlab>=2.2.10 36 | jupyterlab-pygments==0.1.2 37 | jupyterlab-server==1.2.0 38 | kiwisolver==1.3.1 39 | MarkupSafe==1.1.1 40 | matplotlib==3.3.3 41 | mistune==0.8.4 42 | mkl-fft==1.3.0 43 | mkl-random==1.1.1 44 | mkl-service==2.3.0 45 | nbclient==0.5.1 46 | nbconvert>=6.5.1 47 | nbformat==5.0.8 48 | nest-asyncio==1.4.3 49 | notebook>=6.4.1 50 | numpy>=1.22 51 | numpy-stl==2.12.0 52 | olefile==0.46 53 | opencv-python==4.4.0.46 54 | packaging==20.4 55 | pandas==1.2.3 56 | pandocfilters==1.4.3 57 | parso==0.7.1 58 | pickleshare==0.7.5 59 | Pillow==9.4.0 60 | pipenv>=2022.1.8 61 | prometheus-client==0.8.0 62 | prompt-toolkit==3.0.8 63 | pycosat==0.6.3 64 | pycparser==2.20 65 | Pygments==2.7.4 66 | pyOpenSSL==19.1.0 67 | pyparsing==2.4.7 68 | pyrsistent==0.17.3 69 | PySocks==1.7.1 70 | python-box==5.3.0 71 | python-dateutil==2.8.1 72 | python-utils==2.4.0 73 | pytz==2020.1 74 | pywin32>=301 75 | pywinpty==0.5.7 76 | pyyaml==5.4.1 77 | pyzmq==19.0.2 78 | requests==2.23.0 79 | ruamel-yaml==0.15.87 80 | scikit-learn==0.23.2 81 | scipy==1.5.2 82 | seaborn==0.11.1 83 | Send2Trash==1.5.0 84 | Shapely==1.7.1 85 | sip==4.19.13 86 | six==1.14.0 87 | sklearn==0.0 88 | stl==0.0.3 89 | terminado==0.9.1 90 | testpath==0.4.4 91 | threadpoolctl==2.1.0 92 | tornado==6.1 93 | tqdm==4.46.0 94 | traitlets==5.0.5 95 | urllib3>=1.25.9 96 | virtualenv==20.4.3 97 | virtualenv-clone==0.5.4 98 | virtualenvwrapper-win==1.2.6 99 | wcwidth==0.2.5 100 | webencodings==0.5.1 101 | win-inet-pton==1.1.0 102 | wincertstore==0.2 103 | xlrd==1.2.0 --------------------------------------------------------------------------------