get-quick-list.py script is feature complete

This commit is contained in:
Axel Kohlmeyer
2024-08-22 15:41:31 -04:00
parent 1916d0be06
commit 022d1d7959
3 changed files with 264 additions and 140 deletions

View File

@ -4,13 +4,30 @@ Find all example input files containing commands changed in this branch versus d
Companion script to run_tests.py regression tester. Companion script to run_tests.py regression tester.
""" """
import os, re, sys import os, re, sys, subprocess
from glob import glob from pathlib import Path
import subprocess
# infer top level lammps dir if sys.version_info < (3,5):
raise BaseException("Must use at least Python 3.5")
# infer top level LAMMPS dir
LAMMPS_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..')) LAMMPS_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..'))
# ----------------------------------------------------------------------
def changed_files_from_git(branch='develop'):
"""
Return list of changed file from git.
This function queries git to return the list of changed files on
the current branch relative to a given branch (default is 'develop').
param branch: branch to compare with
type branch: string
return: path names of files with changes relative to the repository root
rtype: list of strings
"""
# get list of changed files relative to the develop branch from git # get list of changed files relative to the develop branch from git
output = None output = None
try: try:
@ -20,31 +37,55 @@ except:
pass pass
# collect header files to check for styles # collect header files to check for styles
# - skip files that don't end in '.h' or '.cpp'
# - skip paths that don't start with 'src/'
# - replace '.cpp' with '.h' w/o checking it exists
headers = [] headers = []
# output will have a letter 'A' or 'M' for added or modified files followed by pathname
# append iterms to list and return it
if output: if output:
for changed in output.stdout.decode().split(): for changed in output.stdout.decode().split():
if (changed == 'A') or (changed == 'M'): continue if (changed == 'A') or (changed == 'M'): continue
if not changed.startswith('src/'): continue if not changed.startswith('src/'): continue
if changed.endswith('.h'): headers.append(changed) if changed.endswith('.h'): headers.append(changed)
if changed.endswith('.cpp'): headers.append(changed.replace('.cpp','.h')) if changed.endswith('.cpp'): headers.append(changed.replace('.cpp','.h'))
return headers
# now loop over header files, search for XxxxStyle() macros and append style name # ----------------------------------------------------------------------
command = []
atom = []
compute = []
fix = []
pair = []
body = []
bond = []
angle = []
dihedral = []
improper = []
kspace = []
dump = []
region = []
integrate = []
minimize = []
def get_command_from_header(headers, topdir="."):
"""
Loop over list of header files and extract style names, if present.
LAMMPS commands have macros XxxxStyle() that connects a string with a class.
We search the header files for those macros, extract the string and append
it to a list in a dictionary of different types of styles. We skip over known
suffixes and deprecated commands.
param headers: header files to check for commands
type headers:
return: dictionary with lists of style names
rtype: dict
"""
styles = {}
styles['command'] = []
styles['atom'] = []
styles['compute'] = []
styles['fix'] = []
styles['pair'] = []
styles['body'] = []
styles['bond'] = []
styles['angle'] = []
styles['dihedral'] = []
styles['improper'] = []
styles['kspace'] = []
styles['dump'] = []
styles['region'] = []
styles['integrate'] = []
styles['minimize'] = []
# some regex
style_pattern = re.compile(r"(.+)Style\((.+),(.+)\)") style_pattern = re.compile(r"(.+)Style\((.+),(.+)\)")
upper = re.compile("[A-Z]+") upper = re.compile("[A-Z]+")
gpu = re.compile("(.+)/gpu$") gpu = re.compile("(.+)/gpu$")
@ -56,7 +97,9 @@ opt = re.compile("(.+)/opt$")
removed = re.compile("(.*)Deprecated$") removed = re.compile("(.*)Deprecated$")
for file in headers: for file in headers:
with open(file) as f: # don't fail if file is not present
try:
with open(os.path.join(topdir,file)) as f:
for line in f: for line in f:
matches = style_pattern.findall(line) matches = style_pattern.findall(line)
for m in matches: for m in matches:
@ -90,65 +133,146 @@ for file in headers:
# register style and suffix flags # register style and suffix flags
if m[0] == 'Angle': if m[0] == 'Angle':
angle.append(style) styles['angle'].append(style)
elif m[0] == 'Atom': elif m[0] == 'Atom':
atom.append(style) styles['atom'].append(style)
elif m[0] == 'Body': elif m[0] == 'Body':
register_style(body,style,info) styles['body'].append(style)
elif m[0] == 'Bond': elif m[0] == 'Bond':
bond.applend(style) styles['bond'].applend(style)
elif m[0] == 'Command': elif m[0] == 'Command':
command.append(style) styles['command'].append(style)
elif m[0] == 'Compute': elif m[0] == 'Compute':
compute.append(style) styles['compute'].append(style)
elif m[0] == 'Dihedral': elif m[0] == 'Dihedral':
dihedral.append(style) styles['dihedral'].append(style)
elif m[0] == 'Dump': elif m[0] == 'Dump':
dump.append(style) styles['dump'].append(style)
elif m[0] == 'Fix': elif m[0] == 'Fix':
fix.append(style) styles['fix'].append(style)
elif m[0] == 'Improper': elif m[0] == 'Improper':
improper.append(style) styles['improper'].append(style)
elif m[0] == 'Integrate': elif m[0] == 'Integrate':
integrate.append(style) styles['integrate'].append(style)
elif m[0] == 'KSpace': elif m[0] == 'KSpace':
kspace.append(style) styles['kspace'].append(style)
elif m[0] == 'Minimize': elif m[0] == 'Minimize':
minimize.append(style) styles['minimize'].append(style)
elif m[0] == 'Pair': elif m[0] == 'Pair':
pair.append(style) styles['pair'].append(style)
elif m[0] == 'Region': elif m[0] == 'Region':
region.append(style) styles['region'].append(style)
else: else:
pass pass
# header file not found or not readable
except:
pass
return styles
if len(command): # ----------------------------------------------------------------------
print("Commands: ", '|'.join(command))
if len(atom): def make_regex(styles):
print("Atom styles: ", '|'.join(atom)) """Convert dictionary with styles into a regular expression to scan input files with
if len(compute):
print("Compute styles: ", '|'.join(compute)) This will construct a regular expression matching LAMMPS commands. Ignores continuation
if len(fix):
print("Fix styles: ", '|'.join(fix)) param styles: dictionary with style names
if len(pair): type styles: dict
print("Pair styles: ", '|'.join(pair)) return: compiled regular expression
if len(body): rtype: regex
print("Body styles: ", '|'.join(body)) """
if len(bond):
print("Bond styles: ", '|'.join(bond)) restring = "^\\s*("
if len(angle): if len(styles['command']):
print("Angle styles: ", '|'.join(angle)) restring += '(' + '|'.join(styles['command']) + ')|'
if len(dihedral): if len(styles['atom']):
print("Dihedral styles: ", '|'.join(dihedral)) restring += '(atom_style\\s+(' + '|'.join(styles['atom']) + '))|'
if len(improper): if len(styles['compute']):
print("Improper styles: ", '|'.join(improper)) restring += '(compute\\s+\\S+\\s+\\S+\\s+(' + '|'.join(styles['compute']) + '))|'
if len(kspace): if len(styles['fix']):
print("Kspace styles: ", '|'.join(kspace)) restring += '(fix\\s+\\S+\\s+\\S+\\s+(' + '|'.join(styles['fix']) + '))|'
if len(dump): if len(styles['pair']):
print("Dump styles: ", '|'.join(dump)) restring += '(pair_style\\s+(' + '|'.join(styles['pair']) + '))|'
if len(region): if len(styles['body']):
print("Region styles: ", '|'.join(region)) restring += '(atom_style\\s+body\\s+(' + '|'.join(styles['body']) + '))|'
if len(integrate): if len(styles['bond']):
print("Integrate styles: ", '|'.join(integrate)) restring += '(bond_style\\s+(' + '|'.join(styles['bond']) + '))|'
if len(minimize): if len(styles['angle']):
print("Minimize styles: ", '|'.join(minimize)) restring += '(angle_style\\s+(' + '|'.join(styles['angle']) + '))|'
if len(styles['dihedral']):
restring += '(dihedral_style\\s+(' + '|'.join(styles['dihedral']) + '))|'
if len(styles['improper']):
restring += '(improper_style\\s+(' + '|'.join(styles['improper']) + '))|'
if len(styles['kspace']):
restring += '(kspace_style\\s+(' + '|'.join(styles['kspace']) + '))|'
if len(styles['dump']):
restring += '(dump\\s+\\S+\\s+\\S+\\s+(' + '|'.join(styles['dump']) + '))|'
if len(styles['region']):
restring += '(region\\s+(' + '|'.join(styles['region']) + '))|'
if len(styles['integrate']):
restring += '(run_style\\s+(' + '|'.join(styles['integrate']) + '))|'
if len(styles['minimize']):
restring += '(min_style\\s+(' + '|'.join(styles['minimize']) + '))|'
# replace last (pipe) character with closing parenthesis
length = len(restring)
restring = restring[:length-1] + ')'
# return compiled regex or None
if length > 5:
return re.compile(restring)
else:
return None
# ----------------------------------------------------------------------
def get_examples_using_styles(regex, examples='examples'):
"""
Loop through LAMMPS examples tree and find all files staring with 'in.'
that have at least one line matching the regex.
param regex: pattern matching LAMMPS commands
type regex: compiled regex
param example: path where to start looking for examples recursively
type example: string
return: list of matching example inputs
rtype: list of strings
"""
inputs = []
for filename in Path(examples).rglob('in.*'):
with open(filename) as f:
for line in f:
matches = regex.match(line)
if matches:
inputs.append(filename)
break
return inputs
# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
if __name__ == "__main__":
headers = changed_files_from_git('develop')
styles = get_command_from_header(headers, LAMMPS_DIR)
inputs = get_examples_using_styles(make_regex(styles), os.path.join(LAMMPS_DIR,'examples'))
print("Suggested inputs for testing:")
for inp in inputs:
print(inp)
print("Found changes to the following styles:")
print("Commands: ", styles['command'])
print("Atom styles: ", styles['atom'])
print("Compute styles: ", styles['compute'])
print("Fix styles: ", styles['fix'])
print("Pair styles: ", styles['pair'])
print("Body styles: ", styles['body'])
print("Bond styles: ", styles['bond'])
print("Angle styles: ", styles['angle'])
print("Dihedral styles: ", styles['dihedral'])
print("Improper styles: ", styles['improper'])
print("Kspace styles: ", styles['kspace'])
print("Dump styles: ", styles['dump'])
print("Region styles: ", styles['region'])
print("Integrate styles: ", styles['integrate'])
print("Minimize styles: ", styles['minimize'])