From 2ea281d16355bef6b007d75973fc1841ce2862d8 Mon Sep 17 00:00:00 2001 From: Michael Hines Date: Tue, 13 May 2025 10:18:07 -0400 Subject: [PATCH 1/6] matplotlib windows hangs without manual close. Skip some new models. --- modeldb/modeldb-run.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modeldb/modeldb-run.yaml b/modeldb/modeldb-run.yaml index 35cf1b2..d90b329 100644 --- a/modeldb/modeldb-run.yaml +++ b/modeldb/modeldb-run.yaml @@ -1402,6 +1402,9 @@ skip: true 187473: model_dir: mods + script: + # prevent hanging because of matplotlib window + - sed -i 's,^system,//system,' mosinit.hoc 187604: curate_patterns: - pattern: 'TIME HOST 0: [0-9\.]+ seconds \(set up\)' @@ -1920,6 +1923,18 @@ 2016662: skip: true comment: "Not on GitHub and dirs contain spaces" +2018247: + skip: true + comment: "Not on GitHub and dirs contain spaces" +2018260: + skip: true + comment: "Jupyter notebook. Requires conda" +2018263: + skip: true + comment: "See README.md" +2019342: + skip: true + comment: "branching.mod needs updating" 139150: github: 'pull/1' 185513: From 03f0a7b2e39253d766b64bd73a4179f87f322de4 Mon Sep 17 00:00:00 2001 From: Michael Hines Date: Tue, 13 May 2025 10:19:24 -0400 Subject: [PATCH 2/6] compare_gout_files dir1 dir2 : lists all different gout files show_diff_gout dir1 dir2 : shows them one by one. --- README.md | 12 ++++ modeldb/commands.py | 156 ++++++++++++++++++++++++++++++++++++++++++++ setup.py | 2 + 3 files changed, 170 insertions(+) diff --git a/README.md b/README.md index f9a2cb3..c3f52da 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,18 @@ The following commands are now available: Note that the generated HTML file is self-contained. +* `compare_gout_files` -> list all gout paths that differ between two folders. + ``` + compare_gout_files -h + ``` + Does not require a ```--gout``` arg to ```runmodels```. + + +* `show_diff_gout` -> like `compare_gout_files` but also does diffgout for each. + ``` + show_diff_gout -h + ``` + * `modeldb-config` -> list configuration for `nrn-modeldb-ci`: ``` modeldb-config diff --git a/modeldb/commands.py b/modeldb/commands.py index 88fe420..04344e2 100644 --- a/modeldb/commands.py +++ b/modeldb/commands.py @@ -16,6 +16,53 @@ from .report import diff_reports import json import shutil +import filecmp + + +def _compare_gout_files_internal( + folder1: str, folder2: str +) -> tuple[list[str], list[str], list[str]]: + """ + Internal helper to compare 'gout' files between two folders. + + Args: + folder1 (str): Path to first folder + folder2 (str): Path to second folder + + Returns: + tuple: (files_only_in_folder1, files_only_in_folder2, files_different_content) + """ + # Convert to Path objects + folder1_path = Path(folder1) + folder2_path = Path(folder2) + + # Find all gout files in both folders + gout_files1 = { + str(p.relative_to(folder1_path)) + for p in folder1_path.rglob("gout") + if p.is_file() + } + gout_files2 = { + str(p.relative_to(folder2_path)) + for p in folder2_path.rglob("gout") + if p.is_file() + } + + # Find files unique to each folder + only_in_folder1 = list(gout_files1 - gout_files2) + only_in_folder2 = list(gout_files2 - gout_files1) + + # Find common files that differ in content + common_files = gout_files1 & gout_files2 + different_content = [] + + for file_path in common_files: + file1 = folder1_path / file_path + file2 = folder2_path / file_path + if not filecmp.cmp(file1, file2, shallow=False): + different_content.append(file_path) + + return sorted(only_in_folder1), sorted(only_in_folder2), sorted(different_content) def runmodels(args=None): @@ -327,3 +374,112 @@ def diffreports2html(args=None): == 1 ) return code + + +def compare_gout_files(args=None): + """compare_gout_files + + Compare 'gout' files between two folders and list files that differ. + + Usage: + compare_gout_files + compare_gout_files -h Print help + + Arguments: + folder1=PATH Required: path to first folder containing gout files + folder2=PATH Required: path to second folder containing gout files + + Examples: + compare_gout_files 3246-master 3246-8.0.2 + """ + options = docopt(compare_gout_files.__doc__, args) + + folder1 = options.pop("") + folder2 = options.pop("") + + only_in_folder1, only_in_folder2, different_content = _compare_gout_files_internal( + folder1, folder2 + ) + + # Print results + print("Files only in {}:".format(folder1)) + for f in only_in_folder1: + print(f" {f}") + + print("\nFiles only in {}:".format(folder2)) + for f in only_in_folder2: + print(f" {f}") + + print("\nFiles in both folders with different content:") + for f in different_content: + print(f" {f}") + + # Return status code + return 1 if only_in_folder1 or only_in_folder2 or different_content else 0 + + +def show_diff_gout(args=None): + """show_diff_gout + + Compare 'gout' files between two folders, list differences, and show graphical comparisons one at a time. + + Usage: + show_diff_gout + show_diff_gout -h Print help + + Arguments: + folder1=PATH Required: path to first folder containing gout files + folder2=PATH Required: path to second folder containing gout files + + Examples: + show_diff_gout 8.2.6 827 + """ + options = docopt(show_diff_gout.__doc__, args) + + folder1 = options.pop("") + folder2 = options.pop("") + + # Get the list of differing files + only_in_folder1, only_in_folder2, different_content = _compare_gout_files_internal( + folder1, folder2 + ) + + # Print results (same as compare_gout_files) + print("Files only in {}:".format(folder1)) + for f in only_in_folder1: + print(f" {f}") + + print("\nFiles only in {}:".format(folder2)) + for f in only_in_folder2: + print(f" {f}") + + print("\nFiles in both folders with different content:") + for f in different_content: + print(f" {f}") + + # Show graphical comparisons for files with different content + if different_content: + print( + "\nShowing graphical comparisons (press Enter after closing each NEURON window to continue)..." + ) + folder1_path = Path(folder1) + folder2_path = Path(folder2) + + for file_path in different_content: + gout_file1 = str(folder1_path / file_path) + gout_file2 = str(folder2_path / file_path) + + print(f"\nComparing: {file_path}") + cmd = 'nrngui -c "strdef gout1" -c "gout1=\\"{}\\"" -c "strdef gout2" -c "gout2=\\"{}\\"" modeldb/showgout.hoc'.format( + gout_file1, gout_file2 + ) + commands = shlex.split(cmd) + # Run and wait for the process to complete (user closes NEURON GUI) + process = subprocess.Popen(commands) + process.wait() + + # Prompt for user input to continue + input("Press Enter to continue to the next comparison...") + + # Return status code + return 1 if only_in_folder1 or only_in_folder2 or different_content else 0 diff --git a/setup.py b/setup.py index c7c0a3e..fc95499 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,8 @@ def setup_package(): "modeldb-config = modeldb.commands:modeldb_config", "report2html = modeldb.commands:report2html", "diffreports2html = modeldb.commands:diffreports2html", + "compare_gout_files = modeldb.commands:compare_gout_files", + "show_diff_gout = modeldb.commands:show_diff_gout", ] ), long_description=long_description, From 6667c959d1f8ddd3559dc6ce726e40182a821e5e Mon Sep 17 00:00:00 2001 From: Michael Hines Date: Sun, 8 Jun 2025 11:20:36 -0400 Subject: [PATCH 3/6] a few strings must be raw --- modeldb/report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modeldb/report.py b/modeldb/report.py index 90e3d86..fb4c16a 100644 --- a/modeldb/report.py +++ b/modeldb/report.py @@ -24,8 +24,8 @@ def curate_run_data(run_data, model=None): # nrniv: unable to open font "*helvetica-medium-r-normal*--14*", using "fixed" <-> special: unableto open font "*helvetica-medium-r-normal*--14*", using "fixed" "^nrniv:": "%neuron-executable%:", "^special:": "%neuron-executable%:", - "(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d+\s+\d+:\d+:\d+ [A-Z\s]+ \d+": "%date_command%", - "total run time [0-9\.]+": "total run time %run_time%", + r"(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d+\s+\d+:\d+:\d+ [A-Z\s]+ \d+": "%date_command%", + r"total run time [0-9\.]+": "total run time %run_time%", "(^.*distutils.*$)": "", "/.*?/lib/python.*/site-packages/": "%python-site-packages%", } From b440ce969e7d903efc1c40d73a58b0a69323871d Mon Sep 17 00:00:00 2001 From: Michael Hines Date: Sun, 8 Jun 2025 12:09:13 -0400 Subject: [PATCH 4/6] plots for nrniv runtime (perfcmp1.py) and sim time (zperfcmp.py) --- perfcmp1.py | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ zperfcmp.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 perfcmp1.py create mode 100644 zperfcmp.py diff --git a/perfcmp1.py b/perfcmp1.py new file mode 100644 index 0000000..f0ac24f --- /dev/null +++ b/perfcmp1.py @@ -0,0 +1,66 @@ +# python -i perfcmp1.py 8.2 master +# Plots linear and log-log nrniv runtimes. + +import sys + +xaxis = sys.argv[-2] +yaxis = sys.argv[-1] + + +def rd(logfile): + f = open(logfile, "r") + lines = f.readlines() + data = {} + for line in lines: + if "Done for:" in line: + x = line.split() + try: + x = [x[i] for i in [7, 10, 12]] + x = [x[0], float(x[1][:-1]), float(x[2][:-1])] + data[x[0]] = x[1:] + except: + pass + return data + + +j8 = rd(xaxis + ".log") +master = rd(yaxis + ".log") + +combine = [] +for id in j8: + if id in master: + combine.append([id, j8[id][1], master[id][1]]) + +combine.sort(key=lambda x: x[1]) + + +def f(x, logar): + return h.log10(x) if logar else x + + +from neuron import h, gui + + +def grph(logar): + g = h.Graph() + g.beginline(2, 1) + for x in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]: + g.line(f(x, logar), f(x, logar)) + g.flush() + for i in combine: + g.mark(f(i[1], logar), f(i[2], logar), "O", 6, 1, 1) + g.exec_menu("View = plot") + xlab = "log10(%s time)" % (xaxis,) if logar else "%s time(s)" % (xaxis,) + ylab = "log10(%s time)" % (yaxis,) if logar else "%s time(s)" % (yaxis,) + g.label(f(0.001, logar), f(100.0, logar), ylab, 1, 1, 0, 0, 1) + g.label(f(100.0, logar), f(0.001, logar), xlab, 1, 1, 1, 0, 1) + return g + + +print(f"Number of runmodel {len(j8.values())}") +dat = [j8, master] +for d, name in enumerate([xaxis, yaxis]): + for istyle, style in enumerate(["nrnivmodl", "nrniv"]): + print(f"total {style} for {name} : {sum([x[istyle] for x in dat[d].values()])}") + +z = [grph(0), grph(1)] diff --git a/zperfcmp.py b/zperfcmp.py new file mode 100644 index 0000000..23ed78e --- /dev/null +++ b/zperfcmp.py @@ -0,0 +1,69 @@ +# python -i zperfcmp.py 8.2 master +# Requires nrn branch hines/8.2-runtime and/or hines/master-runtime +# Plots linear and log-log accumulated fadvance, pc.psolve, cvode.solve +# runtimes. + +import json +import sys + +xaxis = sys.argv[-2] +yaxis = sys.argv[-1] + + +def rd(jsfile): + f = open(jsfile, "r") + js = json.load(f) + data = {} + for id in js: + if "nrn_run" in js[id]: + lines = js[id]["nrn_run"] + for line in lines: + if "ZZZZ" in line: + x = line.split() + try: + if x[-2] == "runtime": + data[id] = float(x[-1][:-1]) # eliminate trailing" + except: + pass + return data + + +j8 = rd(xaxis + ".json") +master = rd(yaxis + ".json") + +combine = [] +for id in j8: + if id in master: + combine.append([id, j8[id], master[id]]) + +combine.sort(key=lambda x: x[1]) + + +def f(x, logar): + return h.log10(x) if logar else x + + +from neuron import h, gui + + +def grph(logar): + g = h.Graph() + g.beginline(2, 1) + for x in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]: + g.line(f(x, logar), f(x, logar)) + g.flush() + for i in combine: + g.mark(f(i[1], logar), f(i[2], logar), "O", 6, 1, 1) + g.exec_menu("View = plot") + xlab = "log10(%s time)" % (xaxis,) if logar else "%s time(s)" % (xaxis,) + ylab = "log10(%s time)" % (yaxis,) if logar else "%s time(s)" % (yaxis,) + g.label(f(0.001, logar), f(100.0, logar), ylab, 1, 1, 0, 0, 1) + g.label(f(100.0, logar), f(0.001, logar), xlab, 1, 1, 1, 0, 1) + return g + + +print(f"Number of runtimes {len(combine)}") +for i, name in enumerate([xaxis, yaxis]): + print(f"total runtime for {name} : {sum([x[i + 1] for x in combine])}") + +z = [grph(0), grph(1)] From e159ad49a5e28517051434f0fa156511f04cfc21 Mon Sep 17 00:00:00 2001 From: Michael Hines Date: Tue, 17 Jun 2025 12:26:51 -0400 Subject: [PATCH 5/6] some test info --- modeldb/modeldb-run.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modeldb/modeldb-run.yaml b/modeldb/modeldb-run.yaml index d90b329..6a91612 100644 --- a/modeldb/modeldb-run.yaml +++ b/modeldb/modeldb-run.yaml @@ -1923,6 +1923,11 @@ 2016662: skip: true comment: "Not on GitHub and dirs contain spaces" +2016664: + model_dir: _mod +2017402: + skip: true + comment: "Error: new_seed used as both variable and function in file Gfluc.mod" 2018247: skip: true comment: "Not on GitHub and dirs contain spaces" From 928472ec8ece4ad8b7f6f437f369613b9486462b Mon Sep 17 00:00:00 2001 From: Michael Hines Date: Wed, 17 Sep 2025 08:54:23 -0400 Subject: [PATCH 6/6] model["model_dir"] may be just a single folder name. --- modeldb/modelrun.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modeldb/modelrun.py b/modeldb/modelrun.py index fb03fd3..7e6b36f 100644 --- a/modeldb/modelrun.py +++ b/modeldb/modelrun.py @@ -320,7 +320,13 @@ def run_model(model): if "model_dir" in model: # go over all of the `model_dir`s and group the mod files together for compilation - for model_dir in model["model_dir"]: + # Handle model["model_dir"] as a string or list + model_dirs = ( + model["model_dir"].split(";") + if isinstance(model["model_dir"], str) + else model["model_dir"] + ) + for model_dir in model_dirs: mod_groups.append( find_modfile_group( Path(model.model_dir) / item for item in model_dir.split(";")