Skip to content

Commit e86e619

Browse files
jgray-19jgray-19
andauthored
Additional Tests (#13)
* Add potential fixes for threading * Update __iter__ to be safer, improve documentation, and repr * Update eval * Refactor tests and add lots of more tests * Actually test send_tpsa * new line --------- Co-authored-by: jgray-19 <[email protected]> Co-authored-by: jgray-19 <[email protected]>
1 parent 81f72af commit e86e619

File tree

12 files changed

+1135
-941
lines changed

12 files changed

+1135
-941
lines changed

docs/source/architecture.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ PyMAD-NG supports a wide range of Python-native and NumPy types, which are autom
102102
| `range`, `np.geomspace` | range types | encoded structure |
103103

104104
Conversion is handled by {func}`MAD.send` and `{func}`MAD.recv`, both methods on the {class}`MAD` class.
105+
105106
---
106107

107108
## Dynamic Attributes & Autocompletion

src/pymadng/madp_classes.py

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
from collections.abc import Iterable
1313

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

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

@@ -72,10 +73,7 @@ def __sub__(self, rhs):
7273
def __truediv__(self, rhs):
7374
return self.__generate_operation__(rhs, "/")
7475

75-
def __mod__(self, rhs):
76-
return self.__generate_operation__(rhs, "%")
77-
78-
def __eq__(self, rhs):
76+
def __eq__(self, rhs) -> bool:
7977
if isinstance(rhs, type(self)) and self._name == rhs._name:
8078
return True
8179
else:
@@ -88,21 +86,48 @@ def __generate_operation__(self, rhs, operator: str):
8886
).send(rhs)
8987
return rtrn
9088

91-
def __len__(self):
89+
def __len__(self) -> int:
9290
return self._mad.protected_variable_retrieval(f"#{self._name}")
9391

94-
def __str__(self):
92+
def __str__(self) -> str:
93+
"""
94+
Convert the MAD-NG reference to a string.
95+
This method retrieves the value of the reference and converts it to a string.
96+
If the value is a high-level MAD reference, it returns its string representation.
97+
Otherwise, it returns the string representation of the value.
98+
99+
Returns:
100+
str: The string representation of the MAD-NG reference.
101+
"""
95102
val = self._mad.recv_vars(self._name)
96103
if isinstance(val, high_level_mad_ref):
97104
return repr(val)
98105
else:
99106
return str(val)
100107

101-
def eval(self):
108+
def eval(self) -> Any:
109+
"""
110+
Evaluate the reference and return the value.
111+
112+
Returns:
113+
Any: The evaluated value of the reference in MAD-NG.
114+
"""
102115
return self._mad.recv_vars(self._name)
103116

104-
def __repr__(self): # TODO: This should be better (jgray 2024)
105-
return f"MAD-NG Object(Name: {self._name}, Parent: {self._parent}, Process: {repr(self._mad)})"
117+
def __repr__(self):
118+
"""
119+
Provide a detailed string representation of the MAD-NG object.
120+
121+
Returns:
122+
str: A string containing the object's name, parent, process, and type.
123+
"""
124+
return (
125+
f"<{self.__class__.__name__}("
126+
f"Name: {self._name}, "
127+
f"Parent: {self._parent}, "
128+
f"Process: {repr(self._mad)}, "
129+
f"Type: {type(self).__name__})>"
130+
)
106131

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

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

src/pymadng/madp_object.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -402,20 +402,18 @@ def __getitem__(self, var_name: str) -> Any:
402402

403403
# ----------------------------------------------------------------------------------------------#
404404

405-
def eval(self, input: str) -> Any:
405+
def eval(self, expression: str) -> Any:
406406
"""
407407
Evaluate an expression in the MAD-NG environment.
408408
409-
Assigns the result to a temporary variable and returns its value.
410-
411409
Args:
412-
input (str): The expression to evaluate.
410+
expression (str): The expression to evaluate.
413411
414412
Returns:
415-
The evaluated result.
413+
Any: The result of the evaluated expression.
416414
"""
417415
rtrn = self.__get_mad_reflast()
418-
self.send(f"{rtrn._name} =" + input)
416+
self.send(f"{rtrn._name} = {expression}")
419417
return rtrn.eval()
420418

421419
def evaluate_in_madx_environment(self, input: str) -> None:

src/pymadng/madp_pymad.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import struct
77
import subprocess
88
import sys
9+
import threading
910
from pathlib import Path
1011
from typing import Any, TYPE_CHECKING, TextIO
1112
from contextlib import suppress
@@ -85,16 +86,9 @@ def __init__(
8586
startupChunk = (
8687
f"MAD.pymad '{py_name}' {{_dbg = {lua_debug_flag}}} :__ini({mad_write})"
8788
)
88-
original_sigint_handler = signal.getsignal(signal.SIGINT)
8989

90-
def delete_process(sig, frame):
91-
self.close()
92-
signal.signal(signal.SIGINT, original_sigint_handler)
93-
raise KeyboardInterrupt("MAD process was interrupted, and has been deleted")
94-
95-
signal.signal(
96-
signal.SIGINT, delete_process
97-
) # Delete the process if interrupted
90+
if threading.current_thread() is threading.main_thread():
91+
self._setup_signal_handler()
9892

9993
# Start the process
10094
self.process = subprocess.Popen(
@@ -150,6 +144,16 @@ def delete_process(sig, frame):
150144
self.set_error_handler(True)
151145
self.raise_on_madng_error = True
152146

147+
def _setup_signal_handler(self):
148+
original_sigint_handler = signal.getsignal(signal.SIGINT)
149+
150+
def delete_process(sig, frame):
151+
self.close()
152+
signal.signal(signal.SIGINT, original_sigint_handler)
153+
raise KeyboardInterrupt("MAD process was interrupted, and has been deleted")
154+
155+
signal.signal(signal.SIGINT, delete_process)
156+
153157
def send_range(self, start: float, stop: float, size: int) -> None:
154158
"""Send a linear range (numpy array) to MAD-NG.
155159

0 commit comments

Comments
 (0)