Skip to content

Commit d780a4d

Browse files
authored
Merge pull request #1047 from mickaelbegon/online_mac
macos_online_fix
2 parents 079c5cf + a01ed91 commit d780a4d

10 files changed

Lines changed: 227 additions & 26 deletions

File tree

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -788,14 +788,15 @@ One can refer to their respective solver's documentation to know which options e
788788
The `show_online_optim` parameter can be set to `True` so the graphs nicely update during the optimization with the default values.
789789
One can also directly declare `online_optim` as an `OnlineOptim` parameter to customize the behavior of the plotter.
790790
Note that `show_online_optim` and `online_optim` are mutually exclusive.
791-
Please also note that `OnlineOptim.MULTIPROCESS` is not available on Windows and only none of them are available on Macos.
792-
To see how to run the server on Windows, please refer to the `getting_started/pendulum.py` example.
791+
Please also note that `OnlineOptim.MULTIPROCESS` is not available on Windows or Macos.
792+
On Macos, the default backend is `OnlineOptim.MULTIPROCESS_SERVER`, while `OnlineOptim.SERVER` remains available if one wants to start `resources/plotting_server.py` manually.
793+
To see how to run the server explicitly, please refer to the `resources/plotting_server.py` example.
793794
It is expected to slow down the optimization a bit.
794795
`show_options` can be also passed as a dict to the plotter to customize the plotter's behavior.
795796
If `online_optim` is set to `SERVER`, then a server must be started manually by instantiating an `PlottingServer` class (see `ressources/plotting_server.py`).
796797
The following keys are additional options when using `OnlineOptim.SERVER` and `OnlineOptim.MULTIPROCESS_SERVER`:
797798
- `host`: the host to use (default is `localhost`)
798-
- `port`: the port to use (default is `5030`)
799+
- `port`: the port to use (default is `5030` for `OnlineOptim.SERVER` and a random available port for `OnlineOptim.MULTIPROCESS_SERVER`)
799800

800801
If you want to see IPOPT's iterations over the course of the resolution of your opc, it is possible using the following:
801802
```python
@@ -1720,7 +1721,7 @@ The type of online plotter to use.
17201721

17211722
The accepted values are:
17221723
NONE: No online plotter.
1723-
DEFAULT: Use the default online plotter depending on the OS (MULTIPROCESS on Linux, MULTIPROCESS_SERVER on Windows and NONE on MacOS).
1724+
DEFAULT: Use the default online plotter depending on the OS (MULTIPROCESS on Linux, MULTIPROCESS_SERVER on Windows and macOS).
17241725
MULTIPROCESS: The online plotter is in a separate process.
17251726
SERVER: The online plotter is in a separate server.
17261727
MULTIPROCESS_SERVER: The online plotter using the server automatically setup on a separate process.

bioptim/examples/getting_started/basic_ocp.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,7 @@ def main():
156156
ocp.print(to_console=False, to_graph=False)
157157

158158
# --- Solve the ocp --- #
159-
# Default is OnlineOptim.MULTIPROCESS on Linux, OnlineOptim.MULTIPROCESS_SERVER on Windows and None on MacOS
160-
# To see the graphs on MacOS, one must run the server manually (see resources/plotting_server.py)
159+
# Default is OnlineOptim.MULTIPROCESS_SERVER on all platforms.
161160
solver = Solver.IPOPT(online_optim=OnlineOptim.DEFAULT)
162161

163162
# # Show the constraints Jacobian sparsity
@@ -180,13 +179,16 @@ def main():
180179
sol.print_cost()
181180
# sol.graphs(show_bounds=True, save_name="results.png")
182181

183-
# --- Animate the solution --- #
184-
viewer = "bioviz"
185-
# viewer = "pyorerun"
186-
sol.animate(n_frames=0, viewer=viewer, show_now=True)
182+
# # --- Animate the solution --- #
183+
# # To animate the solution, we can use the animate function. Uncomment the current block
184+
# # to see the animation.
185+
# # 'bioviz' must be installed to use the bioviz viewer.
186+
# # Similarly, 'pyorerun' must be installed to use the pyorerun viewer.
187+
# viewer = "bioviz" # "pyorerun"
188+
# sol.animate(n_frames=0, viewer=viewer, show_now=True)
187189

188190
# # --- Saving the solver's output after the optimization --- #
189-
# Here is an example of how we recommend to save the solution. Please note that sol.ocp is not picklable and that sol will be loaded using the current bioptim version, not the version at the time of the generation of the results.
191+
# # Here is an example of how we recommend to save the solution. Please note that sol.ocp is not picklable and that sol will be loaded using the current bioptim version, not the version at the time of the generation of the results.
190192
# import pickle
191193
# import git
192194
# from datetime import date

bioptim/gui/online_callback_multiprocess_server.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from multiprocessing import Process
2+
import socket
23

34
from .online_callback_server import PlottingServer, OnlineCallbackServer
45

6+
_DEFAULT_HOST = "localhost"
7+
58

69
def _start_server_internal(**kwargs):
710
"""
@@ -15,6 +18,12 @@ def _start_server_internal(**kwargs):
1518
PlottingServer(**kwargs)
1619

1720

21+
def _find_available_tcp_port(host: str) -> int:
22+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
23+
sock.bind((host, 0))
24+
return sock.getsockname()[1]
25+
26+
1827
class OnlineCallbackMultiprocessServer(OnlineCallbackServer):
1928
def __init__(self, *args, **kwargs):
2029
"""
@@ -24,8 +33,12 @@ def __init__(self, *args, **kwargs):
2433
----------
2534
Same as PlottingServer
2635
"""
27-
host = kwargs["host"] if "host" in kwargs else None
28-
port = kwargs["port"] if "port" in kwargs else None
36+
host = kwargs["host"] if "host" in kwargs else _DEFAULT_HOST
37+
port = _find_available_tcp_port(host) if "port" not in kwargs or kwargs["port"] is None else kwargs["port"]
38+
39+
kwargs["host"] = host
40+
kwargs["port"] = port
41+
2942
log_level = None
3043
if "log_level" in kwargs:
3144
log_level = kwargs["log_level"]

bioptim/gui/online_callback_server.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from ..misc.parameters_types import (
1818
Bool,
1919
Int,
20+
Float,
2021
Str,
2122
Bytes,
2223
StrOptional,
@@ -485,7 +486,8 @@ def __init__(
485486

486487
self._host: Str = host if host else _DEFAULT_HOST
487488
self._port: Int = port if port else _DEFAULT_PORT
488-
self._socket: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
489+
self._socket: socket.socket | None = None
490+
self._reset_client_socket()
489491

490492
self._should_wait_ok_to_client_on_new_data: Bool = platform.system() == "Darwin"
491493

@@ -502,6 +504,14 @@ def __init__(
502504

503505
self._initialize_connexion(**show_options)
504506

507+
def _reset_client_socket(self) -> None:
508+
if self._socket is not None:
509+
try:
510+
self._socket.close()
511+
except OSError:
512+
pass
513+
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
514+
505515
def _initialize_connexion(self, retries: Int = 0, **show_options) -> None:
506516
"""
507517
Initializes the connexion to the server
@@ -522,10 +532,11 @@ def _initialize_connexion(self, retries: Int = 0, **show_options) -> None:
522532
if retries > 5:
523533
raise RuntimeError(
524534
"Could not connect to the plotter server, make sure it is running by calling 'PlottingServer()' on "
525-
"another python instance or allowing for automatic start (Linux or Windows) of the server setting "
535+
"another python instance or allowing for automatic start (Linux, Windows or macOS) of the server setting "
526536
"the online_option to 'OnlineOptim.MULTIPROCESS_SERVER' when instantiating your solver."
527537
)
528538
else:
539+
self._reset_client_socket()
529540
time.sleep(1)
530541
return self._initialize_connexion(retries + 1, **show_options)
531542

bioptim/gui/plot.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,8 +1190,13 @@ def _update_ydata(self, ydata: DMList | NpArrayList) -> None:
11901190
y_min = np.inf
11911191
for p in ax.get_children():
11921192
if isinstance(p, lines.Line2D):
1193-
y_min = min(y_min, np.nanmin(p.get_ydata()))
1194-
y_max = max(y_max, np.nanmax(p.get_ydata()))
1193+
y_data = np.asarray(p.get_ydata())
1194+
if y_data.size == 0 or np.isnan(y_data).all():
1195+
continue
1196+
y_min = min(y_min, np.nanmin(y_data))
1197+
y_max = max(y_max, np.nanmax(y_data))
1198+
if not np.isfinite(y_min) or not np.isfinite(y_max):
1199+
continue
11951200
ax.set_ylim(self._compute_ylim(y_min, y_max, 1.25))
11961201

11971202
for p in self.plots_vertical_lines:

bioptim/interfaces/interface_utils.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import platform
12
from time import perf_counter
23

34
from casadi import Importer, Function, horzcat, vertcat, sum1, sum2, nlpsol, SX, MX, DM, reshape, jacobian
@@ -37,7 +38,14 @@ def generic_online_optim(interface: SolverInterface, ocp, show_options: AnyDictO
3738
online_optim = interface.opts.online_optim.get_default()
3839
if online_optim is None:
3940
return
40-
elif online_optim == OnlineOptim.MULTIPROCESS:
41+
if platform.system() == "Darwin" and online_optim == OnlineOptim.MULTIPROCESS:
42+
raise NotImplementedError(
43+
"online_optim MULTIPROCESS is not available on macOS. "
44+
"Use OnlineOptim.MULTIPROCESS_SERVER for automatic plotting or OnlineOptim.SERVER with a manually started "
45+
"PlottingServer."
46+
)
47+
48+
if online_optim == OnlineOptim.MULTIPROCESS:
4149
to_call = OnlineCallbackMultiprocess
4250
elif online_optim == OnlineOptim.SERVER:
4351
to_call = OnlineCallbackServer

bioptim/misc/enums.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class OnlineOptim(Enum):
102102
Attributes
103103
----------
104104
NONE: No online plotting
105-
DEFAULT: Default online plotting (MULTIPROCESS on Linux, MULTIPROCESS_SERVER on Windows and NONE on MacOS)
105+
DEFAULT: Default online plotting (MULTIPROCESS on Linux and MULTIPROCESS_SERVER on Windows and macOS)
106106
MULTIPROCESS: Multiprocess online plotting
107107
SERVER: Server online plotting
108108
MULTIPROCESS_SERVER: Multiprocess server online plotting
@@ -117,9 +117,7 @@ def get_default(self):
117117
if self != OnlineOptim.DEFAULT:
118118
return self
119119

120-
if platform.system() == "Linux":
121-
return OnlineOptim.MULTIPROCESS
122-
elif platform.system() == "Windows":
120+
if platform.system() in ("Linux", "Windows", "Darwin"):
123121
return OnlineOptim.MULTIPROCESS_SERVER
124122
else:
125123
return None

resources/plotting_server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
Since the server runs usings sockets, it is possible to run the server on a different machine than the one running the
88
optimization. This is useful when the optimization is run on a cluster and the plotting server is run on a local machine.
99
10-
On Macos, this server is necessary as it won't connect using multiprocess. One can simply run the current script on
11-
another terminal to access the online graphs
10+
On Macos, this script is still useful if one wants to force OnlineOptim.SERVER explicitly or run the plotting server
11+
on a different machine.
1212
"""
1313

1414
from bioptim import PlottingServer

tests/shard1/test_global_fatigue.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ def test_fatigable_effort_torque_split(phase_dynamics):
629629
sol = ocp.solve()
630630

631631
# Check objective function value
632-
if platform.system() != "Windows":
632+
if platform.system() == "Darwin":
633633
TestUtils.assert_objective_value(sol=sol, expected_value=124.09811263203727)
634634

635635
# Check constraints

0 commit comments

Comments
 (0)