Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs/source/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ PyMAD-NG supports a wide range of Python-native and NumPy types, which are autom
| `range`, `np.geomspace` | range types | encoded structure |

Conversion is handled by {func}`MAD.send` and `{func}`MAD.recv`, both methods on the {class}`MAD` class.

---

## Dynamic Attributes & Autocompletion
Expand Down
48 changes: 38 additions & 10 deletions src/pymadng/madp_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
from collections.abc import Iterable

# TODO: Are you able to store the actual parent? (jgray 2023)
# TODO: Allow __setitem__ to work with multiple indices (Should be a simple recursive loop) (jgray 2023)
# TODO: Allow __setitem__ to work with multiple indices (jgray 2023)
# TODO: Change __iter__ to use the MAD-NG iterator, ipairs and pairs also (jgray 2025)

MADX_methods = ["load", "open_env", "close_env"]

Expand Down Expand Up @@ -72,10 +73,7 @@ def __sub__(self, rhs):
def __truediv__(self, rhs):
return self.__generate_operation__(rhs, "/")

def __mod__(self, rhs):
return self.__generate_operation__(rhs, "%")

def __eq__(self, rhs):
def __eq__(self, rhs) -> bool:
if isinstance(rhs, type(self)) and self._name == rhs._name:
return True
else:
Expand All @@ -88,21 +86,48 @@ def __generate_operation__(self, rhs, operator: str):
).send(rhs)
return rtrn

def __len__(self):
def __len__(self) -> int:
return self._mad.protected_variable_retrieval(f"#{self._name}")

def __str__(self):
def __str__(self) -> str:
"""
Convert the MAD-NG reference to a string.
This method retrieves the value of the reference and converts it to a string.
If the value is a high-level MAD reference, it returns its string representation.
Otherwise, it returns the string representation of the value.

Returns:
str: The string representation of the MAD-NG reference.
"""
val = self._mad.recv_vars(self._name)
if isinstance(val, high_level_mad_ref):
return repr(val)
else:
return str(val)

def eval(self):
def eval(self) -> Any:
"""
Evaluate the reference and return the value.

Returns:
Any: The evaluated value of the reference in MAD-NG.
"""
return self._mad.recv_vars(self._name)

def __repr__(self): # TODO: This should be better (jgray 2024)
return f"MAD-NG Object(Name: {self._name}, Parent: {self._parent}, Process: {repr(self._mad)})"
def __repr__(self):
"""
Provide a detailed string representation of the MAD-NG object.

Returns:
str: A string containing the object's name, parent, process, and type.
"""
return (
f"<{self.__class__.__name__}("
f"Name: {self._name}, "
f"Parent: {self._parent}, "
f"Process: {repr(self._mad)}, "
f"Type: {type(self).__name__})>"
)

def __dir__(self) -> Iterable[str]:
name = self._name
Expand Down Expand Up @@ -164,6 +189,9 @@ def __call__(self, *args, **kwargs):
return last_obj

def __iter__(self):
self._mad.send(f"{self._mad.py_name}:send({self._name}:is_instanceOf(sequence))")
is_seq = self._mad.recv()
assert is_seq, "Iteration is only supported for sequences for now"
self._iterindex = -1
return self

Expand Down
10 changes: 4 additions & 6 deletions src/pymadng/madp_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,20 +402,18 @@ def __getitem__(self, var_name: str) -> Any:

# ----------------------------------------------------------------------------------------------#

def eval(self, input: str) -> Any:
def eval(self, expression: str) -> Any:
"""
Evaluate an expression in the MAD-NG environment.

Assigns the result to a temporary variable and returns its value.

Args:
input (str): The expression to evaluate.
expression (str): The expression to evaluate.

Returns:
The evaluated result.
Any: The result of the evaluated expression.
"""
rtrn = self.__get_mad_reflast()
self.send(f"{rtrn._name} =" + input)
self.send(f"{rtrn._name} = {expression}")
return rtrn.eval()

def evaluate_in_madx_environment(self, input: str) -> None:
Expand Down
22 changes: 13 additions & 9 deletions src/pymadng/madp_pymad.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import struct
import subprocess
import sys
import threading
from pathlib import Path
from typing import Any, TYPE_CHECKING, TextIO
from contextlib import suppress
Expand Down Expand Up @@ -85,16 +86,9 @@ def __init__(
startupChunk = (
f"MAD.pymad '{py_name}' {{_dbg = {lua_debug_flag}}} :__ini({mad_write})"
)
original_sigint_handler = signal.getsignal(signal.SIGINT)

def delete_process(sig, frame):
self.close()
signal.signal(signal.SIGINT, original_sigint_handler)
raise KeyboardInterrupt("MAD process was interrupted, and has been deleted")

signal.signal(
signal.SIGINT, delete_process
) # Delete the process if interrupted
if threading.current_thread() is threading.main_thread():
self._setup_signal_handler()

# Start the process
self.process = subprocess.Popen(
Expand Down Expand Up @@ -150,6 +144,16 @@ def delete_process(sig, frame):
self.set_error_handler(True)
self.raise_on_madng_error = True

def _setup_signal_handler(self):
original_sigint_handler = signal.getsignal(signal.SIGINT)

def delete_process(sig, frame):
self.close()
signal.signal(signal.SIGINT, original_sigint_handler)
raise KeyboardInterrupt("MAD process was interrupted, and has been deleted")

signal.signal(signal.SIGINT, delete_process)

def send_range(self, start: float, stop: float, size: int) -> None:
"""Send a linear range (numpy array) to MAD-NG.

Expand Down
Loading