Skip to content

[PREREQUISITE] Clear reused Matplotlib figures#1081

Merged
pariterre merged 1 commit into
pyomeca:masterfrom
mickaelbegon:codex/fix-matplotlib-reused-figures
Jul 23, 2026
Merged

[PREREQUISITE] Clear reused Matplotlib figures#1081
pariterre merged 1 commit into
pyomeca:masterfrom
mickaelbegon:codex/fix-matplotlib-reused-figures

Conversation

@mickaelbegon

@mickaelbegon mickaelbegon commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • clear the named conditioning figures before recreating their axes
  • clear the named IPOPT-output figure before recreating its axes

Root cause

Recent Matplotlib versions reject plt.subplots(num=...) when a figure with that name already exists unless clear=True is requested. Parametrized tests therefore failed on their second case, independently of the feature PR under test.

Validation

  • pytest tests/shard1/test__global_plots.py::test_plot_check_conditioning tests/shard1/test__global_plots.py::test_plot_check_conditioning_live tests/shard1/test__global_plots.py::test_plot_ipopt_output_live -q
  • 6 passed

This is a small CI prerequisite for the first review wave (#1068, #1069 and #1076).


This change is Reviewable

@mickaelbegon

Copy link
Copy Markdown
Contributor Author

@pariterre, this isolated prerequisite fixes the shared Matplotlib failure seen across the first review wave. The six focused plotting tests pass locally; it contains only three changes.

@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.66667% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 77.26%. Comparing base (627c167) to head (5ed90c1).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
bioptim/gui/ipopt_output_plot.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1081      +/-   ##
==========================================
- Coverage   77.26%   77.26%   -0.01%     
==========================================
  Files         196      196              
  Lines       21508    21506       -2     
==========================================
- Hits        16618    16616       -2     
  Misses       4890     4890              
Flag Coverage Δ
unittests 77.26% <66.66%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mickaelbegon

Copy link
Copy Markdown
Contributor Author

CI update: all 18 macOS/Linux/Windows test jobs pass, along with build, project coverage and merge coverage. Only the external codecov/patch threshold is red; there is no functional test failure. @pariterre, this prerequisite is ready to merge so the three Wave 1 branches can be updated cleanly from master.

@pariterre pariterre left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@mickaelbegon Is "clear" the flag to keep the same behavior as before the update of matplotlib? If I am not mistaken, the expected behavior here is that the subplots stacks on each other (which I am not 100% sure).

@pariterre reviewed 2 files and all commit messages, and made 1 comment.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on mickaelbegon).

@EveCharbie

Copy link
Copy Markdown
Collaborator

Since the online callback with TCP, plot_ipopt_outputs and check_conditioning have been unreachable either with show_online_optim=True or with online_optim=OnlineOptim.DEFAULT on my side.
It is sad because I was using the ipopt output a lot to debug, but it means the changes in this PR will have no effect I think.

@pariterre

Copy link
Copy Markdown
Member

@EveCharbie Does this work with the multiprocess?

@EveCharbie

Copy link
Copy Markdown
Collaborator

@pariterre Yes (on my old env), thanks ! 😅
However, I created a new env and it does not work without and with Mickaël's fix I get:

Traceback (most recent call last):
  File "/home/charbie/Documents/Programmation/BiorbdOptim/bioptim/examples/getting_started/basic_ocp.py", line 239, in <module>
    main()
    ~~~~^^
  File "/home/charbie/Documents/Programmation/BiorbdOptim/bioptim/examples/getting_started/basic_ocp.py", line 165, in main
    sol = ocp.solve(solver)
  File "/home/charbie/Documents/Programmation/BiorbdOptim/bioptim/optimization/optimal_control_program.py", line 1423, in solve
    self.ocp_solver.solve(expand_during_shake_tree=expand_during_shake_tree)
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/charbie/Documents/Programmation/BiorbdOptim/bioptim/interfaces/ipopt_interface.py", line 101, in solve
    return generic_solve(self, expand_during_shake_tree)
  File "/home/charbie/Documents/Programmation/BiorbdOptim/bioptim/interfaces/interface_utils.py", line 147, in generic_solve
    interface.online_optim(interface.ocp, interface.opts.show_options)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/charbie/Documents/Programmation/BiorbdOptim/bioptim/interfaces/ipopt_interface.py", line 85, in online_optim
    generic_online_optim(self, ocp, show_options)
    ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/charbie/Documents/Programmation/BiorbdOptim/bioptim/interfaces/interface_utils.py", line 57, in generic_online_optim
    interface.options_common["iteration_callback"] = to_call(ocp, **show_options)
                                                     ~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/home/charbie/Documents/Programmation/BiorbdOptim/bioptim/gui/online_callback_multiprocess.py", line 34, in __init__
    self.plot_process.start()
    ~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/home/charbie/miniconda3/envs/bioptim_new/lib/python3.14/multiprocessing/process.py", line 121, in start
    self._popen = self._Popen(self)
                  ~~~~~~~~~~~^^^^^^
  File "/home/charbie/miniconda3/envs/bioptim_new/lib/python3.14/multiprocessing/context.py", line 230, in _Popen
    return _default_context.get_context().Process._Popen(process_obj)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
  File "/home/charbie/miniconda3/envs/bioptim_new/lib/python3.14/multiprocessing/context.py", line 306, in _Popen
    return Popen(process_obj)
  File "/home/charbie/miniconda3/envs/bioptim_new/lib/python3.14/multiprocessing/popen_forkserver.py", line 35, in __init__
    super().__init__(process_obj)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
  File "/home/charbie/miniconda3/envs/bioptim_new/lib/python3.14/multiprocessing/popen_fork.py", line 20, in __init__
    self._launch(process_obj)
    ~~~~~~~~~~~~^^^^^^^^^^^^^
  File "/home/charbie/miniconda3/envs/bioptim_new/lib/python3.14/multiprocessing/popen_forkserver.py", line 47, in _launch
    reduction.dump(process_obj, buf)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/home/charbie/miniconda3/envs/bioptim_new/lib/python3.14/multiprocessing/reduction.py", line 60, in dump
    ForkingPickler(file, protocol).dump(obj)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^
  File "/home/charbie/miniconda3/envs/bioptim_new/lib/python3.14/site-packages/casadi/casadi.py", line 13684, in __getstate__
    raise Exception("Cannot pickle SX objects without a casadi context. " +
    ...<2 lines>...
      "  pickle.dump(f,open(filename,'wb'))")
Exception: Cannot pickle SX objects without a casadi context. Use something like:
with ca.global_pickle_context(): 
  pickle.dump(f,open(filename,'wb'))
when serializing dict item 'dt'
when serializing bioptim.optimization.non_linear_program.NonLinearProgram state
when serializing bioptim.optimization.non_linear_program.NonLinearProgram object
when serializing list item 0
when serializing dict item 'nlp'
when serializing bioptim.optimization.optimal_control_program.OptimalControlProgram state
when serializing bioptim.optimization.optimal_control_program.OptimalControlProgram object
when serializing dict item '_ocp'
when serializing bioptim.gui.online_callback_multiprocess.OnlineCallbackMultiprocess.ProcessPlotter state
when serializing bioptim.gui.online_callback_multiprocess.OnlineCallbackMultiprocess.ProcessPlotter object
when serializing dict item '_target'
when serializing multiprocessing.context.Process state
when serializing multiprocessing.context.Process object

I don't understand what changed :/

@mickaelbegon

Copy link
Copy Markdown
Contributor Author

what I understood is that using clear=True reuses the named Matplotlib window while removing axes and plots left by previous calls. This prevents overlapping subplot grids, avoids unnecessary memory growth, and ensures each diagnostic displays only the current result.

@pariterre

Copy link
Copy Markdown
Member

@EveCharbie This is not related to the current changes. It is related to an update of Casadi. Since 3.7.0. (if I recall well), MX/SX can no longer be pickled. This means sending values to the multiprocess backend won't work.

This is one of the reasons I pushed the DEFAULT to SERVER at some point. There is probably a fix, but it can be hard to implement (i.e. starting the multiprocess and recreating the full MX/SX tree from scratch in graph side)

@EveCharbie

Copy link
Copy Markdown
Collaborator

Yes, that was my suspicion too.
So we can merge this PR in the mean time beacause the code is unrunable, so I cannot confirm that the new behavior is the same as the old one...

@pariterre

Copy link
Copy Markdown
Member

@mickaelbegon This is my understanding too, but I think the graphs were meant to overlap unless we were relying on a memory leak (which is totally possible). If the graphs are meant to be replaced, then the clear=True is actually fixing a bug we had, but if the graphs are meant to overlap (e.g. with transparency or white spaces filled with previous values), then clear=True will wipe out the previous graphs.
I do not know what was the intended behavior with these graphs (I did not use much "check_conditionning")

@EveCharbie

Copy link
Copy Markdown
Collaborator

Yes clear=True replacing the graph is the expected behavior and should be fine :)

@pariterre
pariterre merged commit f0c54a7 into pyomeca:master Jul 23, 2026
23 of 24 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.

3 participants