Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RTM] New plotting! #887

Merged
merged 17 commits into from
Aug 5, 2024
Merged

[RTM] New plotting! #887

merged 17 commits into from
Aug 5, 2024

Conversation

pariterre
Copy link
Member

@pariterre pariterre commented Aug 1, 2024

New way of plotting using a server B-)


This change is Reviewable

Copy link
Collaborator

@Ipuch Ipuch left a comment

Choose a reason for hiding this comment

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

Great !

Reviewed 14 of 14 files at r1, all commit messages.
Reviewable status: all files reviewed, 37 unresolved discussions (waiting on @pariterre)


README.md line 794 at r1 (raw file):

One can refer to their respective solver's documentation to know which options exist.
The `show_online_optim` parameter can be set to `True` so the graphs nicely update during the optimization.
Please note that `ShowOnlineType.MULTIPROCESS` is not available on Windows. To see how to run the server on Windows, please refer to the `getting_started/pendulum.py` example.

ShowOnlineType.MULTITHREAD ?


README.md line 798 at r1 (raw file):

`show_options` can be also passed as a dict to the plotter to customize the plotter's behavior.
The following keys are special options:
  - `type`: the type of plotter to use (default is `ShowOnlineType.MULTIPROCESS`)

idem

Code quote:

ShowOnlineType.MULTIPROCESS

README.md line 1692 at r1 (raw file):

The accepted values are:
MULTIPROCESS: The online plotter is in a separate process.

MULTITHREAD


bioptim/examples/getting_started/pendulum.py line 155 at r1 (raw file):

    # --- Solve the ocp --- #
    if platform.system() == "Windows":
        # The default online type (ShowOnlineType.MULTIPROCESS) is not available on Windows

MULTITHREAD


bioptim/examples/getting_started/pendulum.py line 157 at r1 (raw file):

        # The default online type (ShowOnlineType.MULTIPROCESS) is not available on Windows
        # Since `as_multiprocess` default value is True, it will automatically starts the server in the background
        solver = Solver.IPOPT(show_online_optim=True, show_options={"type": ShowOnlineType.SERVER})

OnlineOptim.None # False
OnlineOptim.MULTIPROCESS
OnlineOptim.DEFAULT # True, specific for each Plateform !
OnlineOptim.MULTIPROCESS_SERVER
OnlineOptim.SERVER


bioptim/gui/online_callback_multiprocess.py line 60 at r1 (raw file):

            The multiprocessing queue to evaluate
        plot: PlotOcp
            The handler on all the figures

you can update

Code quote:

        pipe: mp.Queue
            The multiprocessing queue to evaluate
        plot: PlotOcp
            The handler on all the figures

bioptim/gui/online_callback_multiprocess.py line 118 at r1 (raw file):

            for fig in self._plotter.all_figures:
                fig.canvas.draw()
            if [plt.fignum_exists(fig.number) for fig in self._plotter.all_figures].count(True) > 0:

has_at_least_one_active_figure =

Code quote:

[plt.fignum_exists(fig.number) for fig in self._plotter.all_figures].count(True)

bioptim/gui/online_callback_server.py line 18 at r1 (raw file):

_default_host = "localhost"

_DEFAULT_HOST + _DEFAULT_PORT


bioptim/gui/online_callback_server.py line 42 at r1 (raw file):

class _ServerMessages(Enum):

EnumInt ?


bioptim/gui/online_callback_server.py line 95 at r1 (raw file):

    @staticmethod
    def as_multiprocess(*args, **kwargs) -> None:

A class would abstract better the behavior. -> "PlottingMultiProcessServer"


bioptim/gui/online_callback_server.py line 105 at r1 (raw file):

        from multiprocessing import Process

        thread = Process(target=_start_as_multiprocess_internal, args=args, kwargs=kwargs)

process = ...


bioptim/gui/online_callback_server.py line 110 at r1 (raw file):

    def _run(self) -> None:
        """
        Starts the server, this method can be called directly by the user to start a plot server

update doc


bioptim/gui/online_callback_server.py line 154 at r1 (raw file):

            self._logger.warning("Client closed connexion")
            client_socket.close()
            return _ServerMessages.CLOSE_CONNEXION, None

get_data_length()

Code quote:

        # Receive the actual data
        try:
            self._logger.debug("Waiting for data from client")
            data = client_socket.recv(1024)
            if not data:
                return _ServerMessages.EMPTY, None
        except:
            self._logger.warning("Client closed connexion")
            client_socket.close()
            return _ServerMessages.CLOSE_CONNEXION, None

bioptim/gui/online_callback_server.py line 168 at r1 (raw file):

                data_out.append(client_socket.recv(len_data))
                if len(data_out[-1]) != len_data:
                    data_out[-1] += client_socket.recv(len_data - len(data_out[-1]))

Can go with the function above

Code quote:

                if len(data_out[-1]) != len_data:
                    data_out[-1] += client_socket.recv(len_data - len(data_out[-1]))

bioptim/gui/online_callback_server.py line 256 at r1 (raw file):

            fig.canvas.draw()

        if [plt.fignum_exists(fig.number) for fig in self._plotter.all_figures].count(True) > 0:

has_at_least_one_active_plot =

Code quote:

[plt.fignum_exists(fig.number) for fig in self._plotter.all_figures].count(True) > 0

bioptim/gui/online_callback_server.py line 291 at r1 (raw file):

                return

        elif message_type == _ServerMessages.EMPTY or message_type == _ServerMessages.CLOSE_CONNEXION:

message_type in (Enum1, Enum2)


bioptim/gui/online_callback_server.py line 298 at r1 (raw file):

            timer_get_data.start()

    def _update_data(self, data_raw: list) -> None:

jsonlike_data ?


bioptim/gui/online_callback_server.py line 305 at r1 (raw file):

        ----------
        data_raw: list
            The raw data from the client

see Also ?


bioptim/gui/online_callback_server.py line 435 at r1 (raw file):

            raise RuntimeError("The server did not acknowledge the connexion")

        # TODO ADD SHOW OPTIONS to the send

Done


bioptim/gui/online_callback_server.py line 458 at r1 (raw file):

        self._socket.close()

    def eval(self, arg: list | tuple, force: bool = False) -> list:

Enforce, blocking ?


bioptim/gui/online_callback_server.py line 513 at r1 (raw file):

                header += f",{y_steps.shape[0]}"
                y_steps_tp = y_steps.tolist()
                data_serialized += struct.pack("d" * len(y_steps_tp), *y_steps_tp)

Put it into a function called "xydata_encoder" with "xydata_decoder" up there ideal for testing !

Code quote:

        args_dict = {}
        for i, s in enumerate(nlpsol_out()):
            args_dict[s] = arg[i]
        xdata_raw, ydata_raw = self._plotter.parse_data(**args_dict)

        header = f"{len(xdata_raw)}"
        data_serialized = b""
        for x_nodes in xdata_raw:
            header += f",{len(x_nodes)}"
            for x_steps in x_nodes:
                header += f",{x_steps.shape[0]}"
                x_steps_tp = np.array(x_steps)[:, 0].tolist()
                data_serialized += struct.pack("d" * len(x_steps_tp), *x_steps_tp)

        header += f",{len(ydata_raw)}"
        for y_nodes_variable in ydata_raw:
            if isinstance(y_nodes_variable, np.ndarray):
                header += f",0"
                y_nodes_variable = [y_nodes_variable]
            else:
                header += f",{len(y_nodes_variable)}"

            for y_steps in y_nodes_variable:
                header += f",{y_steps.shape[0]}"
                y_steps_tp = y_steps.tolist()
                data_serialized += struct.pack("d" * len(y_steps_tp), *y_steps_tp)

bioptim/gui/plot.py line 294 at r1 (raw file):

                fig.canvas.draw()
                if self.plot_options["general_options"]["use_tight_layout"]:
                    fig.tight_layout()

def spread_the_figures_on_screen(...)

Code quote:

            horz = 0
            vert = 1 if len(self.all_figures) < self.n_vertical_windows * self.n_horizontal_windows else 0
            for i, fig in enumerate(self.all_figures):
                if self.automatically_organize:
                    try:
                        fig.canvas.manager.window.move(
                            int(vert * self.width_step), int(self.top_margin + horz * self.height_step)
                        )
                        vert += 1
                        if vert >= self.n_vertical_windows:
                            horz += 1
                            vert = 0
                    except AttributeError:
                        pass
                fig.canvas.draw()
                if self.plot_options["general_options"]["use_tight_layout"]:
                    fig.tight_layout()

bioptim/gui/plot.py line 455 at r1 (raw file):

                        if not y_min_all[y_range_var_idx]:
                            y_min_all[y_range_var_idx] = [np.inf] * nb_subplots
                            y_max_all[y_range_var_idx] = [-np.inf] * nb_subplots

def initializes_all_axes(...)

Code quote:

                    if nlp.plot[variable].combine_to:
                        self.axes[variable] = self.axes[nlp.plot[variable].combine_to]
                        axes = self.axes[variable][1]
                    elif i > 0 and variable in self.axes:
                        axes = self.axes[variable][1]
                    else:
                        nb_subplots = max(
                            [
                                (
                                    max(
                                        len(nlp.plot[variable].phase_mappings.to_first.map_idx),
                                        max(nlp.plot[variable].phase_mappings.to_first.map_idx) + 1,
                                    )
                                    if variable in nlp.plot
                                    else 0
                                )
                                for nlp in self.ocp.nlp
                            ]
                        )

                        # TODO: get rid of all_variables_in_one_subplot by fixing the mapping appropriately
                        if not nlp.plot[variable].all_variables_in_one_subplot:
                            n_cols, n_rows = PlotOcp._generate_windows_size(nb_subplots)
                        else:
                            n_cols = 1
                            n_rows = 1
                        axes = self._add_new_axis(variable, nb_subplots, n_rows, n_cols)
                        self.axes[variable] = [nlp.plot[variable], axes]

                        if not y_min_all[y_range_var_idx]:
                            y_min_all[y_range_var_idx] = [np.inf] * nb_subplots
                            y_max_all[y_range_var_idx] = [-np.inf] * nb_subplots

bioptim/gui/plot.py line 786 at r1 (raw file):

    def update_data(
        self,
        xdata: dict,

list | np.ndarray


bioptim/gui/plot.py line 795 at r1 (raw file):

        Parameters
        ----------
        xdata: dict

list


bioptim/gui/serializable_class.py line 263 at r1 (raw file):

                penalty = custom_plot.parameters[key]

                casadi_function = penalty.function[0] if penalty.function[0] is not None else penalty.function[-1]

emulated_casadi_function ?


bioptim/gui/serializable_class.py line 402 at r1 (raw file):

class OdeSolverSerializable:

todo: adjust base on if your plot is failing.


bioptim/gui/serializable_class.py line 417 at r1 (raw file):

        return cls(
            polynomial_degree=5,

true value here ?


bioptim/interfaces/interface_utils.py line 27 at r1 (raw file):

    show_options: dict
        The options to pass to PlotOcp, special options are:
            - type: ShowOnlineType.MULTIPROCESS or ShowOnlineType.SERVER

fix names


bioptim/interfaces/interface_utils.py line 30 at r1 (raw file):

            - host: The host to connect to (only for ShowOnlineType.SERVER)
            - port: The port to connect to (only for ShowOnlineType.SERVER)
            - as_multiprocess: If the server should run as a multiprocess (only for ShowOnlineType.SERVER), if True,

as_multiprocess should be gone


bioptim/interfaces/interface_utils.py line 36 at r1 (raw file):

        show_options = {}

    show_type = ShowOnlineType.MULTIPROCESS

idem


bioptim/interfaces/interface_utils.py line 42 at r1 (raw file):

    if show_type == ShowOnlineType.MULTIPROCESS:
        if platform == "win32":

if not linux


bioptim/misc/enums.py line 109 at r1 (raw file):

    MULTIPROCESS = 0
    SERVER = 1

should have the all Enums


bioptim/optimization/optimization_vector.py line 359 at r1 (raw file):

            A reference to the ocp
        data: np.ndarray | DM
            The solution in a vector, if no data is provided, dummy data is used (it can be useful getting the dimensions)

not true anymore


bioptim/gui/online_callback_abstract.py line 66 at r1 (raw file):

        # There must be an option to add an if here
        from ..interfaces.ipopt_interface import IpoptInterface

Let's try once if it works outside.


bioptim/gui/online_callback_abstract.py line 158 at r1 (raw file):

    @abstractmethod
    def eval(self, arg: list | tuple, force: bool = False) -> list:

list of int ?


bioptim/gui/online_callback_abstract.py line 166 at r1 (raw file):

        arg: list | tuple
            The data to send

Add force in the doc string

Copy link
Collaborator

@Ipuch Ipuch left a comment

Choose a reason for hiding this comment

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

Reviewable status: all files reviewed, 37 unresolved discussions (waiting on @pariterre)


bioptim/gui/online_callback_server.py line 513 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

Put it into a function called "xydata_encoder" with "xydata_decoder" up there ideal for testing !

Add a test for encoding decoding part.

Copy link
Member Author

@pariterre pariterre left a comment

Choose a reason for hiding this comment

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

Reviewable status: all files reviewed, 37 unresolved discussions (waiting on @Ipuch)


README.md line 794 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

ShowOnlineType.MULTITHREAD ?

Done.


README.md line 798 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

idem

Done.


README.md line 1692 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

MULTITHREAD

Done.


bioptim/examples/getting_started/pendulum.py line 155 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

MULTITHREAD

Done.


bioptim/examples/getting_started/pendulum.py line 157 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

OnlineOptim.None # False
OnlineOptim.MULTIPROCESS
OnlineOptim.DEFAULT # True, specific for each Plateform !
OnlineOptim.MULTIPROCESS_SERVER
OnlineOptim.SERVER

Done.


bioptim/gui/online_callback_multiprocess.py line 60 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

you can update

Done.


bioptim/gui/online_callback_multiprocess.py line 118 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

has_at_least_one_active_figure =

Done.


bioptim/gui/online_callback_server.py line 18 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

_DEFAULT_HOST + _DEFAULT_PORT

Done.


bioptim/gui/online_callback_server.py line 42 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

EnumInt ?

Done.


bioptim/gui/online_callback_server.py line 95 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

A class would abstract better the behavior. -> "PlottingMultiProcessServer"

Done.


bioptim/gui/online_callback_server.py line 105 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

process = ...

Done.


bioptim/gui/online_callback_server.py line 110 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

update doc

Done.


bioptim/gui/online_callback_server.py line 256 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

has_at_least_one_active_plot =

Done.


bioptim/gui/online_callback_server.py line 291 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

message_type in (Enum1, Enum2)

Done.


bioptim/gui/online_callback_server.py line 298 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

jsonlike_data ?

serialized_raw_data


bioptim/gui/online_callback_server.py line 305 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

see Also ?

Done.


bioptim/gui/online_callback_server.py line 435 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

Done

Done.


bioptim/gui/online_callback_server.py line 458 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

Enforce, blocking ?

Done.


bioptim/gui/plot.py line 294 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

def spread_the_figures_on_screen(...)

Done.


bioptim/gui/plot.py line 786 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

list | np.ndarray

Done.


bioptim/gui/plot.py line 795 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

list

Done.


bioptim/gui/serializable_class.py line 263 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

emulated_casadi_function ?

My bad, at that point it is the actual casadi_function


bioptim/gui/serializable_class.py line 402 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

todo: adjust base on if your plot is failing.

Done.


bioptim/gui/serializable_class.py line 417 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

true value here ?

Done.


bioptim/interfaces/interface_utils.py line 27 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

fix names

Done.


bioptim/interfaces/interface_utils.py line 30 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

as_multiprocess should be gone

Done.


bioptim/interfaces/interface_utils.py line 36 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

idem

Done.


bioptim/interfaces/interface_utils.py line 42 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

if not linux

Done.


bioptim/misc/enums.py line 109 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

should have the all Enums

Done.


bioptim/optimization/optimization_vector.py line 359 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

not true anymore

Done.


bioptim/gui/online_callback_abstract.py line 66 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

Let's try once if it works outside.

Nope


bioptim/gui/online_callback_abstract.py line 158 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

list of int ?

Done.


bioptim/gui/online_callback_abstract.py line 166 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

Add force in the doc string

Done.

Copy link
Member Author

@pariterre pariterre left a comment

Choose a reason for hiding this comment

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

Reviewable status: 1 of 30 files reviewed, 37 unresolved discussions (waiting on @Ipuch)


bioptim/gui/online_callback_server.py line 154 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

get_data_length()

Separated in two subfunctions


bioptim/gui/online_callback_server.py line 168 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

Can go with the function above

Done.


bioptim/gui/online_callback_server.py line 513 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

Add a test for encoding decoding part.

Done.


bioptim/gui/plot.py line 455 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

def initializes_all_axes(...)

This part actually defines 4 different variables needed later. I feel unconfortable moving this part without proper testing

@pariterre
Copy link
Member Author

@Ipuch Ready to merge B-)

@pariterre pariterre changed the title New plotting! [RTM] New plotting! Aug 2, 2024
Copy link
Collaborator

@Ipuch Ipuch left a comment

Choose a reason for hiding this comment

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

Reviewed 20 of 28 files at r2, 11 of 11 files at r3, all commit messages.
Reviewable status: all files reviewed, 2 unresolved discussions (waiting on @pariterre)


bioptim/gui/online_callback_server.py line 435 at r1 (raw file):

Previously, pariterre (Pariterre) wrote…

Done.

The comment wasn't supposed to be gone ?

Copy link
Member Author

@pariterre pariterre left a comment

Choose a reason for hiding this comment

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

Reviewable status: all files reviewed, 2 unresolved discussions (waiting on @Ipuch)


bioptim/gui/online_callback_server.py line 435 at r1 (raw file):

Previously, Ipuch (Pierre Puchaud) wrote…

The comment wasn't supposed to be gone ?

Done

Copy link
Collaborator

@Ipuch Ipuch left a comment

Choose a reason for hiding this comment

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

:lgtm:

Reviewed 1 of 1 files at r4, all commit messages.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on @pariterre)

@Ipuch Ipuch merged commit a690c8b into pyomeca:master Aug 5, 2024
19 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants