Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
3fad621
place function for lattice parser in own file
proy30 May 31, 2025
aad51c8
break parse_lattice_elements into smaller helper functions
proy30 May 31, 2025
f9af77f
refactor: parse_cleaned_lattice
proy30 May 31, 2025
ea1e80d
add: collect_lattice_operations
proy30 May 31, 2025
37052e6
add: inline_element_variables
proy30 May 31, 2025
cffe7ed
add: flatten_variable_definition
proy30 Jun 1, 2025
5fbdf67
rebase for updating parse_cleaned_lattice
proy30 Jun 1, 2025
e7e10a1
update: parse_lattice_elements to utilize helper functions
proy30 Jun 1, 2025
af1b7c5
update naming: flatten variable list def
proy30 Jun 1, 2025
42de35a
add test: run/fodo.py
proy30 Jun 1, 2025
b3bc78e
add test: cyclotron
proy30 Jun 1, 2025
b527496
add test: dogleg
proy30 Jun 1, 2025
cce57cb
change if/else block to 'match'
proy30 Jun 1, 2025
0ae6eee
change static class to instance-based
proy30 Jun 1, 2025
ff0916f
fix indentation for match
proy30 Jun 1, 2025
8789634
fix: parsing blocking_factor
proy30 Jun 1, 2025
3b08807
update debug print statement for flatten
proy30 Jun 1, 2025
d0bf944
improve: flatten_variable_list_definitions
proy30 Jun 1, 2025
fd37c15
update collect_lattice_operations
proy30 Jun 1, 2025
87e3b72
rename flatton function name
proy30 Jun 1, 2025
f449595
add tests: expanding_fft_lattice
proy30 Jun 1, 2025
61a2eb5
update function name
proy30 Jun 1, 2025
af2f50a
add .reverse() functionality/chicane test
proy30 Jun 1, 2025
7068e92
add tests: apochromatic & expanding_fft - lattice builds
proy30 Jun 1, 2025
01a3039
add docstring to class
proy30 Jun 1, 2025
13271e7
update typends
proy30 Jun 1, 2025
ebdf01b
add: caching for saving recursive time
proy30 Jun 1, 2025
240ca26
separately find used lattice variables
proy30 Jun 3, 2025
733b0f7
update: docstrings/comments
proy30 Jun 3, 2025
41d9d85
store file_content in lattice class' initialization
proy30 Jun 3, 2025
eeaa9d1
add docstring
proy30 Jun 3, 2025
58e0204
cache content hash
proy30 Jun 3, 2025
10373fc
fix parser - distribution funciton
proy30 Jun 28, 2025
105e646
delete wrongly added file from rebase
proy30 Jun 28, 2025
49a95f4
revise naming for lattice vars/class/functions
proy30 Jun 28, 2025
2cd0f48
simplify
proy30 Jun 28, 2025
adb9618
support .revsese()
proy30 Sep 20, 2025
3ea0c92
fix/cleanup
proy30 Sep 20, 2025
f226783
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2025
583c6d3
relocate get_impactx_dir function
proy30 Sep 25, 2025
6833a6c
duplicate get_impactx_root_dir in tests utils
proy30 Sep 25, 2025
3c04630
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/python/impactx/dashboard/Input/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
License: BSD-3-Clause-LBNL
"""

from pathlib import Path
from typing import Union

from .. import state
Expand Down Expand Up @@ -139,3 +140,18 @@
current_input = getattr(state, state_name)
numeric_input = GeneralFunctions.convert_to_numeric(current_input)
setattr(state, state_name, numeric_input)

def get_impactx_root_dir() -> Path | None:

Check notice

Code scanning / CodeQL

First parameter of a method is not named 'self' Note

Normal methods should have at least one parameter (the first of which should be 'self').
"""
Locates the ImpactX source directory.
Looks for the outermost parent directory named 'impactx' that contains a '.git' folder.
"""

current_directory = Path(__file__).resolve()
root_dir = None

for parent_dir in current_directory.parents:
Comment on lines +151 to +154
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@proy30 don't you already have a method for this from #876? :)

Please merge development and check if this is duplicate now :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping @proy30 don't you already have a method for this from #876? :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ax3l Thank you for the ping. Yes, there is a duplicate function in both this PR (#1140) and #876.

At first, this was done intentionally when added in because (trying to recall correctly) it was difficult to have the pytests (from the directory impactx/tests/python/dashboard/utils.py call to the function in directory impactx/src/python/impactx/dashboard/Input/utils.py.

Any suggestions for where the function get_impactx_root_dir should be placed? It needs to be accessed by both the dashboard and the dashboard tests. Unsure if, in the future, the functionality could be used elsewhere where the root impactx directory is needed.

if parent_dir.name == "impactx" and (parent_dir / ".git").is_dir():
root_dir = parent_dir # keep going until we reach the highest match
Comment on lines +155 to +156
Copy link
Member

@ax3l ax3l Oct 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@proy30 it looks to me like you are quite often assuming that ImpactX is used from its source tree. This is, outside of development, not the case.

We install ImpactX like this:

cmake -S . -B build -DImpactX_PYTHON=ON
cmake --build build -j 4 --target pip_install

which copies things outside of the source tree. Thus, looking for a .git/ directory is not a logic that will work when ImpactX was installed/deployed/is used in production.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ax3l Thank you for pointing this out. Will add a commit to work well in both development and non-development

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will utilise the old path and logic from get_impactx_path() that you mention in the comment below.

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! :)

return root_dir
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ def update_length_statistics() -> None:
@staticmethod
def update_element_counts() -> dict[str, int]:
"""
Computes the element counts in the lattice list.
Computes the element counts in the lattice list
and stores them in descending order by count.

:return: Dictionary of element names and their counts, sorted by count descending.
:return: Dictionary of element counts indexed by element name.
"""
counts = {}
for element in state.selected_lattice_list:
Expand Down
23 changes: 12 additions & 11 deletions src/python/impactx/dashboard/Toolbar/examples_loader/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@

DASHBOARD_EXAMPLES = {
"fodo/run_fodo.py",
# "chicane/run_chicane_csr.py",
# "fodo_space_charge/run_fodo_envelope_sc.py",
# "apochromatic/run_apochromatic.py",
# "kurth/run_kurth_10nC_periodic.py",
# "expanding_beam/run_expanding_fft.py",
# "expanding_beam/run_expanding_envelope.py",
# "iota_lattice/run_iotalattice.py",
# "cyclotron/run_cyclotron.py",
# "dogleg/run_dogleg.py",
"chicane/run_chicane_csr.py",
"fodo_space_charge/run_fodo_envelope_sc.py",
"apochromatic/run_apochromatic.py",
# "kurth/run_kurth_10nC_periodic.py", - running into recursion issues
"expanding_beam/run_expanding_fft.py",
"expanding_beam/run_expanding_envelope.py",
"iota_lattice/run_iotalattice.py",
"cyclotron/run_cyclotron.py",
"dogleg/run_dogleg.py",
}


Expand Down Expand Up @@ -64,7 +64,7 @@ def _get_example_content(file_name: str) -> dict:
Retrieve the selected ImpactX example file and populate the UI with its values.
"""

impactx_directory = DashboardExamplesLoader.get_impactx_path()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep using the old path and logic from get_impactx_path(), I fixed the logic in #1179

impactx_directory = GeneralFunctions.get_impactx_root_dir()
impactx_example_file_path = impactx_directory / "examples" / file_name

file_content_as_str = impactx_example_file_path.read_text()
Expand All @@ -81,8 +81,9 @@ def load_impactx_examples() -> None:

state.impactx_example_list.clear()

impactx_directory = DashboardExamplesLoader.get_impactx_path()
impactx_directory = GeneralFunctions.get_impactx_root_dir()
impactx_examples_directory = impactx_directory / "examples"
print(" the examples directory is ", impactx_examples_directory)

for path in impactx_examples_directory.glob("**/run*"):
relative_path = path.relative_to(impactx_examples_directory)
Expand Down
Empty file.
64 changes: 32 additions & 32 deletions src/python/impactx/dashboard/Toolbar/file_imports/python/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ def parse_single_inputs(content: str) -> dict:
pattern_match = re.search(pattern.format(parameter_name), content)
if pattern_match:
value = ast.literal_eval(pattern_match.group(1))

if parameter_name == "space_charge" and isinstance(value, bool):
value = str(value).lower()

reference_dictionary[parameter_name] = value
break

Expand All @@ -61,8 +65,14 @@ def parse_single_inputs(content: str) -> dict:
r"\bkin_energy_MeV\s*=\s*([^#\n]+)", content
)
if kin_energy_pattern_match:
kin_energy_value = kin_energy_pattern_match.group(1)
reference_dictionary["kin_energy_on_ui"] = kin_energy_value
kin_energy_value = kin_energy_pattern_match.group(1).strip()

try:
parsed_value = ast.literal_eval(kin_energy_value)
except (ValueError, SyntaxError):
parsed_value = reference_dictionary.get("kin_energy_MeV")

reference_dictionary["kin_energy_on_ui"] = parsed_value

return reference_dictionary

Expand All @@ -71,11 +81,24 @@ def parse_list_inputs(content: str) -> dict:
"""
Parses list-based simulation parameters from the simulation file content.

Some of the dashboard inputs are lists, such as n_cell = [16, 16, 16].
We have to look inside of the brackets to retrieve the individual values.

:param content: The content of the ImpactX simulation file.
"""
dictionary = {}
list_inputs = ["n_cell", "prob_relative"]
list_parsing = "{} = (\\[.*?\\])"
list_inputs = [
"n_cell",
"prob_relative",
"blocking_factor_x",
"blocking_factor_y",
"blocking_factor_z",
]

# The below regex will capture everything inside of the brackets
# it assumes that the list will be placed in a single line (not multiple).
# Example: n_cell = [16, 16, 16] --> [16, 16, 16]
list_parsing = r"{}\s*=\s*(\[[^\]]*\])"

for input_name in list_inputs:
match = re.search(list_parsing.format(input_name), content)
Expand All @@ -86,9 +109,13 @@ def parse_list_inputs(content: str) -> dict:
for i, dim in enumerate(["x", "y", "z"]):
dictionary[f"n_cell_{dim}"] = values[i]

if input_name == "prob_relative":
elif input_name == "prob_relative":
dictionary["prob_relative"] = values

elif input_name.startswith("blocking_factor_"):
# Convert [16] -> 16
dictionary[input_name] = values[0]

return dictionary

@staticmethod
Expand Down Expand Up @@ -134,33 +161,6 @@ def extract_parameters(distribution_type, parsing_pattern):

return dictionary

@staticmethod
def parse_lattice_elements(content: str) -> dict:
"""
Parses lattice elements from the simulation file content.

:param content: The content of the ImpactX simulation file.
"""

dictionary = {"lattice_elements": []}
used_variables = set()

lattice_elements = re.findall(r"elements\.(\w+)\((.*?)\)", content)

for element_name, element_parameter in lattice_elements:
element = {"element": element_name, "parameters": {}}

parameter_pairs = re.findall(r"(\w+)=([^,\)]+)", element_parameter)
for parameter_name, parameter_value in parameter_pairs:
parameter_value_cleaned = parameter_value.strip("'\"")
element["parameters"][parameter_name] = parameter_value_cleaned
used_variables.add(parameter_value_cleaned)

dictionary["lattice_elements"].append(element)

dictionary["used_lattice_variables"] = used_variables
return dictionary

@staticmethod
def parse_variables(content: str, used_vars: set) -> dict:
"""
Expand Down
Loading
Loading