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,151 +4,275 @@ Find all example input files containing commands changed in this branch versus d
Companion script to run_tests.py regression tester.
"""
import os, re, sys
from glob import glob
import subprocess
import os, re, sys, subprocess
from pathlib import Path
# 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__), '..', '..'))
# get list of changed files relative to the develop branch from git
output = None
try:
output = subprocess.run('git diff --diff-filter=MA --name-status develop',
shell=True, capture_output=True)
except:
pass
# ----------------------------------------------------------------------
# collect header files to check for styles
headers = []
if output:
for changed in output.stdout.decode().split():
if (changed == 'A') or (changed == 'M'): continue
if not changed.startswith('src/'): continue
if changed.endswith('.h'): headers.append(changed)
if changed.endswith('.cpp'): headers.append(changed.replace('.cpp','.h'))
def changed_files_from_git(branch='develop'):
"""
Return list of changed file from git.
# 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 = []
This function queries git to return the list of changed files on
the current branch relative to a given branch (default is 'develop').
style_pattern = re.compile(r"(.+)Style\((.+),(.+)\)")
upper = re.compile("[A-Z]+")
gpu = re.compile("(.+)/gpu$")
intel = re.compile("(.+)/intel$")
kokkos = re.compile("(.+)/kk$")
kokkos_skip = re.compile("(.+)/kk/(host|device)$")
omp = re.compile("(.+)/omp$")
opt = re.compile("(.+)/opt$")
removed = re.compile("(.*)Deprecated$")
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
"""
for file in headers:
with open(file) as f:
for line in f:
matches = style_pattern.findall(line)
for m in matches:
# skip over internal styles w/o explicit documentation
style = m[1]
if upper.match(style):
continue
# get list of changed files relative to the develop branch from git
output = None
try:
output = subprocess.run('git diff --diff-filter=MA --name-status develop',
shell=True, capture_output=True)
except:
pass
# skip over suffix styles:
suffix = kokkos_skip.match(style)
if suffix:
continue
suffix = gpu.match(style)
if suffix:
continue
suffix = intel.match(style)
if suffix:
continue
suffix = kokkos.match(style)
if suffix:
continue
suffix = omp.match(style)
if suffix:
continue
suffix = opt.match(style)
if suffix:
continue
deprecated = removed.match(m[2])
if deprecated:
continue
# 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 = []
# 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:
for changed in output.stdout.decode().split():
if (changed == 'A') or (changed == 'M'): continue
if not changed.startswith('src/'): continue
if changed.endswith('.h'): headers.append(changed)
if changed.endswith('.cpp'): headers.append(changed.replace('.cpp','.h'))
return headers
# register style and suffix flags
if m[0] == 'Angle':
angle.append(style)
elif m[0] == 'Atom':
atom.append(style)
elif m[0] == 'Body':
register_style(body,style,info)
elif m[0] == 'Bond':
bond.applend(style)
elif m[0] == 'Command':
command.append(style)
elif m[0] == 'Compute':
compute.append(style)
elif m[0] == 'Dihedral':
dihedral.append(style)
elif m[0] == 'Dump':
dump.append(style)
elif m[0] == 'Fix':
fix.append(style)
elif m[0] == 'Improper':
improper.append(style)
elif m[0] == 'Integrate':
integrate.append(style)
elif m[0] == 'KSpace':
kspace.append(style)
elif m[0] == 'Minimize':
minimize.append(style)
elif m[0] == 'Pair':
pair.append(style)
elif m[0] == 'Region':
region.append(style)
else:
pass
# ----------------------------------------------------------------------
if len(command):
print("Commands: ", '|'.join(command))
if len(atom):
print("Atom styles: ", '|'.join(atom))
if len(compute):
print("Compute styles: ", '|'.join(compute))
if len(fix):
print("Fix styles: ", '|'.join(fix))
if len(pair):
print("Pair styles: ", '|'.join(pair))
if len(body):
print("Body styles: ", '|'.join(body))
if len(bond):
print("Bond styles: ", '|'.join(bond))
if len(angle):
print("Angle styles: ", '|'.join(angle))
if len(dihedral):
print("Dihedral styles: ", '|'.join(dihedral))
if len(improper):
print("Improper styles: ", '|'.join(improper))
if len(kspace):
print("Kspace styles: ", '|'.join(kspace))
if len(dump):
print("Dump styles: ", '|'.join(dump))
if len(region):
print("Region styles: ", '|'.join(region))
if len(integrate):
print("Integrate styles: ", '|'.join(integrate))
if len(minimize):
print("Minimize styles: ", '|'.join(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\((.+),(.+)\)")
upper = re.compile("[A-Z]+")
gpu = re.compile("(.+)/gpu$")
intel = re.compile("(.+)/intel$")
kokkos = re.compile("(.+)/kk$")
kokkos_skip = re.compile("(.+)/kk/(host|device)$")
omp = re.compile("(.+)/omp$")
opt = re.compile("(.+)/opt$")
removed = re.compile("(.*)Deprecated$")
for file in headers:
# don't fail if file is not present
try:
with open(os.path.join(topdir,file)) as f:
for line in f:
matches = style_pattern.findall(line)
for m in matches:
# skip over internal styles w/o explicit documentation
style = m[1]
if upper.match(style):
continue
# skip over suffix styles:
suffix = kokkos_skip.match(style)
if suffix:
continue
suffix = gpu.match(style)
if suffix:
continue
suffix = intel.match(style)
if suffix:
continue
suffix = kokkos.match(style)
if suffix:
continue
suffix = omp.match(style)
if suffix:
continue
suffix = opt.match(style)
if suffix:
continue
deprecated = removed.match(m[2])
if deprecated:
continue
# register style and suffix flags
if m[0] == 'Angle':
styles['angle'].append(style)
elif m[0] == 'Atom':
styles['atom'].append(style)
elif m[0] == 'Body':
styles['body'].append(style)
elif m[0] == 'Bond':
styles['bond'].applend(style)
elif m[0] == 'Command':
styles['command'].append(style)
elif m[0] == 'Compute':
styles['compute'].append(style)
elif m[0] == 'Dihedral':
styles['dihedral'].append(style)
elif m[0] == 'Dump':
styles['dump'].append(style)
elif m[0] == 'Fix':
styles['fix'].append(style)
elif m[0] == 'Improper':
styles['improper'].append(style)
elif m[0] == 'Integrate':
styles['integrate'].append(style)
elif m[0] == 'KSpace':
styles['kspace'].append(style)
elif m[0] == 'Minimize':
styles['minimize'].append(style)
elif m[0] == 'Pair':
styles['pair'].append(style)
elif m[0] == 'Region':
styles['region'].append(style)
else:
pass
# header file not found or not readable
except:
pass
return styles
# ----------------------------------------------------------------------
def make_regex(styles):
"""Convert dictionary with styles into a regular expression to scan input files with
This will construct a regular expression matching LAMMPS commands. Ignores continuation
param styles: dictionary with style names
type styles: dict
return: compiled regular expression
rtype: regex
"""
restring = "^\\s*("
if len(styles['command']):
restring += '(' + '|'.join(styles['command']) + ')|'
if len(styles['atom']):
restring += '(atom_style\\s+(' + '|'.join(styles['atom']) + '))|'
if len(styles['compute']):
restring += '(compute\\s+\\S+\\s+\\S+\\s+(' + '|'.join(styles['compute']) + '))|'
if len(styles['fix']):
restring += '(fix\\s+\\S+\\s+\\S+\\s+(' + '|'.join(styles['fix']) + '))|'
if len(styles['pair']):
restring += '(pair_style\\s+(' + '|'.join(styles['pair']) + '))|'
if len(styles['body']):
restring += '(atom_style\\s+body\\s+(' + '|'.join(styles['body']) + '))|'
if len(styles['bond']):
restring += '(bond_style\\s+(' + '|'.join(styles['bond']) + '))|'
if len(styles['angle']):
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'])