Repository: csu-hmc/opty
Pull request #310: Allow separate eom violation plots. replaces PR #273
Author: login: Peter230655
Created: 2025-01-28T15:14:24Z | Merged: 2025-03-22T11:06:13Z

This allows to see the violations of the equations of motion in detail, each in a separate plot, if the new optional keyword _detailed_eoms_ is set to True. If set to False or not set, the eom violations will be displayed as before.
This replaces PR #273 which does not meet the last test.
I have tried it on many simulations, and at times found substantial differences in the violations of the eoms, say 10^(-8) for the worst to 10^(-18) for the best is no unusual.


Diff:
diff --git a/examples-gallery/advanced/plot_human_gait.py b/examples-gallery/advanced/plot_human_gait.py
index b23b0ef2..9a9fd50b 100644
--- a/examples-gallery/advanced/plot_human_gait.py
+++ b/examples-gallery/advanced/plot_human_gait.py
@@ -45,7 +45,6 @@
 Import all necessary modules, functions, and classes:
 """
 import os
-import pprint
 from pkg_resources import parse_version
 from opty import Problem
 from opty.utils import f_minus_ma
@@ -117,7 +116,7 @@
 # The constants are loaded from a file of realistic geometry, mass, inertia,
 # and foot deformation properties of an adult human.
 par_map = simulate.load_constants(constants, 'human-gait-constants.yml')
-pprint.pprint(par_map)
+par_map
 
 # %%
 # gait2d provides "hand of god" inputs to manipulate the trunk for some
@@ -372,7 +371,7 @@ def update(i):
 # Now see what the solution looks like in the Moon's gravitational field.
 g = constants[0]
 prob.collocator.known_parameter_map[g] = 1.625  # m/s**2
-pprint.pprint(prob.collocator.known_parameter_map)
+prob.collocator.known_parameter_map
 
 # %%
 # Use earth's solution as initial guess.
diff --git a/examples-gallery/advanced/plot_sit_to_stand.py b/examples-gallery/advanced/plot_sit_to_stand.py
index 0fab8cc9..ad5ef824 100644
--- a/examples-gallery/advanced/plot_sit_to_stand.py
+++ b/examples-gallery/advanced/plot_sit_to_stand.py
@@ -21,7 +21,6 @@
 Import all necessary modules, functions, and classes:
 """
 import os
-import pprint
 from opty import Problem
 from opty.utils import f_minus_ma
 from pygait2d import derive, simulate
@@ -88,7 +87,7 @@
 # The constant values are loaded from a file of realistic geometry, mass,
 # inertia, and foot deformation properties of an adult human.
 par_map = simulate.load_constants(constants, 'human-gait-constants.yml')
-pprint.pprint(par_map)
+par_map
 
 # %%
 # gait2d provides "hand of god" inputs to manipulate the trunk for some
diff --git a/examples-gallery/beginner/plot_pendulum_swing_up_variable_duration.py b/examples-gallery/beginner/plot_pendulum_swing_up_variable_duration.py
index f6ee0d2a..6fdaf355 100644
--- a/examples-gallery/beginner/plot_pendulum_swing_up_variable_duration.py
+++ b/examples-gallery/beginner/plot_pendulum_swing_up_variable_duration.py
@@ -8,6 +8,7 @@
 - Demonstrate how to make the simulation duration variable.
 - Show how to use the NumPy backend which solves the problem without needing
   just-in-time C compilation.
+- Demonstrate plotting the constraint violations as subplots.
 
 Introduction
 ------------
@@ -117,7 +118,7 @@ def obj_grad(free):
 
 # %%
 # Plot the constraint violations.
-_ = prob.plot_constraint_violations(solution)
+_ = prob.plot_constraint_violations(solution, subplots=True)
 
 # %%
 # Animate the pendulum swing up.
diff --git a/opty/direct_collocation.py b/opty/direct_collocation.py
index 3daf29dc..fea1265d 100644
--- a/opty/direct_collocation.py
+++ b/opty/direct_collocation.py
@@ -563,7 +563,7 @@ def plot_trajectories(self, vector, axes=None):
         return axes
 
     @_optional_plt_dep
-    def plot_constraint_violations(self, vector, axes=None):
+    def plot_constraint_violations(self, vector, axes=None, subplots=False):
         """Returns an axis with the state constraint violations plotted versus
         node number and the instance constraints as a bar graph.
 
@@ -572,6 +572,14 @@ def plot_constraint_violations(self, vector, axes=None):
         vector : ndarray, (n*N + q*N + r + s, )
             The initial guess, solution, or any other vector that is in the
             canonical form.
+        axes : ndarray of AxesSubplot, optional.
+            If given, it is the user's responsibility to provide the correct
+            number of axes.
+        subplots : boolean, optional.
+            If True, the equations of motion will be plotted in a separate plot
+            for each equation of motion. The default is False. If a user wants
+            to provide the axes, it is recommended to run once without
+            providing axes, to see how many are needed.
 
         Returns
         =======
@@ -594,6 +602,14 @@ def plot_constraint_violations(self, vector, axes=None):
         bars_per_plot = None
         rotation = -45
 
+        if subplots:
+            figsize = 1.25
+        else:
+            figsize = 1.75
+
+        if not isinstance(figsize, float):
+            raise ValueError('figsize given must be a float.')
+
         # find the number of bars per plot, so the bars per plot are
         # aproximately the same on each plot
         hilfs = []
@@ -650,16 +666,35 @@ def plot_constraint_violations(self, vector, axes=None):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            fig, axes = plt.subplots(1 + num_plots, 1,
-                                     figsize=(6.4, 1.50*(1 + num_plots)),
-                                     layout='compressed')
+            if subplots is False or self.collocator.num_states == 1:
+                num_eom_plots = 1
+            else:
+                num_eom_plots = self.collocator.num_states
+
+            fig, axes = plt.subplots(num_eom_plots + num_plots, 1,
+                                     figsize=(6.4, figsize*(num_eom_plots +
+                                                            num_plots)),
+                                     layout='constrained')
+
+        else:
+            num_eom_plots = len(axes) - num_plots
 
         axes = np.asarray(axes).ravel()
 
-        axes[0].plot(con_nodes, state_violations.T)
-        axes[0].set_title('Constraint violations')
-        axes[0].set_xlabel('Node Number')
-        axes[0].set_ylabel('EoM violation')
+        if subplots is False or self.collocator.num_states == 1:
+            axes[0].plot(con_nodes, state_violations.T)
+            axes[0].set_title('Constraint violations')
+            axes[0].set_xlabel('Node Number')
+            axes[0].set_ylabel('EoM violation')
+
+        else:
+            for i in range(self.collocator.num_states):
+                axes[i].plot(con_nodes, state_violations[i])
+                axes[i].set_ylabel(f'Eq. {str(i+1)} \n violation', fontsize=9)
+                if i < self.collocator.num_states - 1:
+                    axes[i].set_xticklabels([])
+            axes[num_eom_plots-1].set_xlabel('Node Number')
+            axes[0].set_title('Constraint violations')
 
         if self.collocator.instance_constraints is not None:
             # reduce the instance constrtaints to 2 digits after the decimal
@@ -702,12 +737,13 @@ def plot_constraint_violations(self, vector, axes=None):
                 inst_constr = instance_constr_plot[beginn: endd]
 
                 width = [0.06*num_ticks for _ in range(num_ticks)]
-                axes[i+1].bar(range(num_ticks), inst_viol,
-                              tick_label=[sm.latex(s, mode='inline') for s in
-                                          inst_constr], width=width)
-                axes[i+1].set_ylabel('Instance')
-                axes[i+1].set_xticklabels(axes[i+1].get_xticklabels(),
-                                          rotation=rotation)
+                axes[i+num_eom_plots].bar(
+                    range(num_ticks), inst_viol,
+                    tick_label=[sm.latex(s, mode='inline') for s in
+                                inst_constr], width=width)
+                axes[i+num_eom_plots].set_ylabel('Instance')
+                axes[i+num_eom_plots].set_xticklabels(
+                    axes[i+num_eom_plots].get_xticklabels(), rotation=rotation)
 
         return axes
 


Reviews:
user: login: moorepants
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T07:18:00Z
user: login: moorepants
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T07:20:39Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T07:20:46Z
user: login: moorepants
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T07:21:12Z
user: login: moorepants
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T07:21:39Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T07:23:51Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T07:26:06Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T07:27:09Z
user: login: moorepants
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T07:43:51Z
user: login: moorepants
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T07:44:45Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T07:49:32Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T07:54:44Z
user: login: moorepants
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T07:59:19Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T08:01:55Z
user: login: moorepants
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-22T08:42:11Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T08:47:01Z
user: login: moorepants
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-22T08:48:29Z
user: login: moorepants
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-22T08:49:46Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T09:06:34Z
user: login: moorepants
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-22T09:08:01Z
user: login: moorepants
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-22T09:08:43Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T09:10:41Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T09:20:52Z
user: login: moorepants
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-22T09:33:01Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T09:37:16Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T10:12:54Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T10:13:38Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T10:16:33Z
user: login: moorepants
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-22T10:16:51Z
user: login: Peter230655
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T10:19:55Z

Review comments:
diff_hunk: @@ -438,7 +438,7 @@ def plot_trajectories(self, vector, axes=None):
         return axes
 
     @_optional_plt_dep
-    def plot_constraint_violations(self, vector, axes=None):
+    def plot_constraint_violations(self, vector, axes=None, detailed_eoms=False):
path: opty/direct_collocation.py
user: login: moorepants
body: I think `subplots=True/False` is a better name for this.
created_at: 2025-03-21T07:18:00Z
author_association: MEMBER
diff_hunk: @@ -525,16 +531,43 @@ def plot_constraint_violations(self, vector, axes=None):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            fig, axes = plt.subplots(1 + num_plots, 1,
-                                     figsize=(6.4, 1.50*(1 + num_plots)),
-                                     layout='compressed')
+            if detailed_eoms == False or self.collocator.num_states == 1:
+                num_eom_plots = 1
+            else:
+                num_eom_plots = self.collocator.num_states
+            fig, axes = plt.subplots(num_eom_plots + num_plots, 1,
+                            figsize=(6.4, 1.75*(num_eom_plots + num_plots)),
path: opty/direct_collocation.py
user: login: moorepants
body: Why have you made the plots taller? It is already problematic if there many rows of constraints.
created_at: 2025-03-21T07:20:38Z
author_association: MEMBER
diff_hunk: @@ -438,7 +438,7 @@ def plot_trajectories(self, vector, axes=None):
         return axes
 
     @_optional_plt_dep
-    def plot_constraint_violations(self, vector, axes=None):
+    def plot_constraint_violations(self, vector, axes=None, detailed_eoms=False):
path: opty/direct_collocation.py
user: login: Peter230655
body: > I think `subplots=True/False` is a better name for this.

I agree, will change it.
created_at: 2025-03-21T07:20:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -525,16 +531,43 @@ def plot_constraint_violations(self, vector, axes=None):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            fig, axes = plt.subplots(1 + num_plots, 1,
-                                     figsize=(6.4, 1.50*(1 + num_plots)),
-                                     layout='compressed')
+            if detailed_eoms == False or self.collocator.num_states == 1:
+                num_eom_plots = 1
+            else:
+                num_eom_plots = self.collocator.num_states
+            fig, axes = plt.subplots(num_eom_plots + num_plots, 1,
+                            figsize=(6.4, 1.75*(num_eom_plots + num_plots)),
+                            constrained_layout=True)
path: opty/direct_collocation.py
user: login: moorepants
body: I thought his is `layout='constrained'`
created_at: 2025-03-21T07:21:12Z
author_association: MEMBER
diff_hunk: @@ -525,16 +531,43 @@ def plot_constraint_violations(self, vector, axes=None):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            fig, axes = plt.subplots(1 + num_plots, 1,
-                                     figsize=(6.4, 1.50*(1 + num_plots)),
-                                     layout='compressed')
+            if detailed_eoms == False or self.collocator.num_states == 1:
+                num_eom_plots = 1
+            else:
+                num_eom_plots = self.collocator.num_states
+            fig, axes = plt.subplots(num_eom_plots + num_plots, 1,
+                            figsize=(6.4, 1.75*(num_eom_plots + num_plots)),
+                            constrained_layout=True)
+
+        else:
+            num_eom_plots = len(axes) - num_plots
 
         axes = np.asarray(axes).ravel()
 
-        axes[0].plot(con_nodes, state_violations.T)
-        axes[0].set_title('Constraint violations')
-        axes[0].set_xlabel('Node Number')
-        axes[0].set_ylabel('EoM violation')
+        if detailed_eoms == False or self.collocator.num_states == 1:
+            axes[0].plot(con_nodes, state_violations.T)
+            axes[0].set_title('Constraint violations')
+            axes[0].set_xlabel('Node Number')
+            axes[0].set_ylabel('EoM violation')
+
+        else:
+            for i in range(self.collocator.num_states):
+                k = i + 1
+                if k in (11,12,13):
+                    msg = 'th'
+                elif k % 10 == 1:
+                    msg = 'st'
+                elif k % 10 == 2:
+                    msg = 'nd'
+                elif k % 10 == 3:
+                    msg = 'rd'
+                else:
+                    msg = 'th'
+
+                axes[i].plot(con_nodes, state_violations[i])
+                axes[i].set_ylabel(f'{str(k)}-{msg} EOM violation')
path: opty/direct_collocation.py
user: login: moorepants
body: Just use `Eq. 1`, `Eq. 2`.
created_at: 2025-03-21T07:21:38Z
author_association: MEMBER
diff_hunk: @@ -525,16 +531,43 @@ def plot_constraint_violations(self, vector, axes=None):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            fig, axes = plt.subplots(1 + num_plots, 1,
-                                     figsize=(6.4, 1.50*(1 + num_plots)),
-                                     layout='compressed')
+            if detailed_eoms == False or self.collocator.num_states == 1:
+                num_eom_plots = 1
+            else:
+                num_eom_plots = self.collocator.num_states
+            fig, axes = plt.subplots(num_eom_plots + num_plots, 1,
+                            figsize=(6.4, 1.75*(num_eom_plots + num_plots)),
+                            constrained_layout=True)
path: opty/direct_collocation.py
user: login: Peter230655
body: > I thought his is `layout='constrained'`

I do not recall why I did it, it has been a while.
I assumed I did it like this so the plots would not get too small with large number of eoms. Need to double check.
created_at: 2025-03-21T07:23:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -525,16 +531,43 @@ def plot_constraint_violations(self, vector, axes=None):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            fig, axes = plt.subplots(1 + num_plots, 1,
-                                     figsize=(6.4, 1.50*(1 + num_plots)),
-                                     layout='compressed')
+            if detailed_eoms == False or self.collocator.num_states == 1:
+                num_eom_plots = 1
+            else:
+                num_eom_plots = self.collocator.num_states
+            fig, axes = plt.subplots(num_eom_plots + num_plots, 1,
+                            figsize=(6.4, 1.75*(num_eom_plots + num_plots)),
+                            constrained_layout=True)
+
+        else:
+            num_eom_plots = len(axes) - num_plots
 
         axes = np.asarray(axes).ravel()
 
-        axes[0].plot(con_nodes, state_violations.T)
-        axes[0].set_title('Constraint violations')
-        axes[0].set_xlabel('Node Number')
-        axes[0].set_ylabel('EoM violation')
+        if detailed_eoms == False or self.collocator.num_states == 1:
+            axes[0].plot(con_nodes, state_violations.T)
+            axes[0].set_title('Constraint violations')
+            axes[0].set_xlabel('Node Number')
+            axes[0].set_ylabel('EoM violation')
+
+        else:
+            for i in range(self.collocator.num_states):
+                k = i + 1
+                if k in (11,12,13):
+                    msg = 'th'
+                elif k % 10 == 1:
+                    msg = 'st'
+                elif k % 10 == 2:
+                    msg = 'nd'
+                elif k % 10 == 3:
+                    msg = 'rd'
+                else:
+                    msg = 'th'
+
+                axes[i].plot(con_nodes, state_violations[i])
+                axes[i].set_ylabel(f'{str(k)}-{msg} EOM violation')
path: opty/direct_collocation.py
user: login: Peter230655
body: > Just use `Eq. 1`, `Eq. 2`.

MUCH better, will change.
created_at: 2025-03-21T07:26:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -525,16 +531,43 @@ def plot_constraint_violations(self, vector, axes=None):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            fig, axes = plt.subplots(1 + num_plots, 1,
-                                     figsize=(6.4, 1.50*(1 + num_plots)),
-                                     layout='compressed')
+            if detailed_eoms == False or self.collocator.num_states == 1:
+                num_eom_plots = 1
+            else:
+                num_eom_plots = self.collocator.num_states
+            fig, axes = plt.subplots(num_eom_plots + num_plots, 1,
+                            figsize=(6.4, 1.75*(num_eom_plots + num_plots)),
path: opty/direct_collocation.py
user: login: Peter230655
body: > Why have you made the plots taller? It is already problematic if there many rows of constraints.

Will try to make them smaller, maybe 1.5 will work.
created_at: 2025-03-21T07:27:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -448,6 +448,12 @@ def plot_constraint_violations(self, vector, axes=None):
             The initial guess, solution, or any other vector that is in the
             canonical form.
 
+        detailed_eoms : boolean, optional. If True, the equations of motion
+            will be plotted in a separate plot for each state. Default is False.
+
+        axes : ndarray of AxesSubplot, optional. If given, it is the user's
+            responsibility to provide the correct number of axes.
path: opty/direct_collocation.py
user: login: moorepants
body: You need to give a clue of what the number of subplots show be if subplots is true or false
created_at: 2025-03-21T07:43:51Z
author_association: MEMBER
diff_hunk: @@ -525,16 +531,43 @@ def plot_constraint_violations(self, vector, axes=None):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            fig, axes = plt.subplots(1 + num_plots, 1,
-                                     figsize=(6.4, 1.50*(1 + num_plots)),
-                                     layout='compressed')
+            if detailed_eoms == False or self.collocator.num_states == 1:
+                num_eom_plots = 1
+            else:
+                num_eom_plots = self.collocator.num_states
+            fig, axes = plt.subplots(num_eom_plots + num_plots, 1,
+                            figsize=(6.4, 1.75*(num_eom_plots + num_plots)),
+                            constrained_layout=True)
+
+        else:
+            num_eom_plots = len(axes) - num_plots
 
         axes = np.asarray(axes).ravel()
 
-        axes[0].plot(con_nodes, state_violations.T)
-        axes[0].set_title('Constraint violations')
-        axes[0].set_xlabel('Node Number')
-        axes[0].set_ylabel('EoM violation')
+        if detailed_eoms == False or self.collocator.num_states == 1:
+            axes[0].plot(con_nodes, state_violations.T)
+            axes[0].set_title('Constraint violations')
+            axes[0].set_xlabel('Node Number')
+            axes[0].set_ylabel('EoM violation')
+
+        else:
+            for i in range(self.collocator.num_states):
+                k = i + 1
+                if k in (11,12,13):
+                    msg = 'th'
+                elif k % 10 == 1:
+                    msg = 'st'
+                elif k % 10 == 2:
+                    msg = 'nd'
+                elif k % 10 == 3:
+                    msg = 'rd'
+                else:
+                    msg = 'th'
path: opty/direct_collocation.py
user: login: moorepants
body: What is this elaborate if statement? It looks like something that would be prone to fail for arbitrary inputs.
created_at: 2025-03-21T07:44:45Z
author_association: MEMBER
diff_hunk: @@ -525,16 +531,43 @@ def plot_constraint_violations(self, vector, axes=None):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            fig, axes = plt.subplots(1 + num_plots, 1,
-                                     figsize=(6.4, 1.50*(1 + num_plots)),
-                                     layout='compressed')
+            if detailed_eoms == False or self.collocator.num_states == 1:
+                num_eom_plots = 1
+            else:
+                num_eom_plots = self.collocator.num_states
+            fig, axes = plt.subplots(num_eom_plots + num_plots, 1,
+                            figsize=(6.4, 1.75*(num_eom_plots + num_plots)),
+                            constrained_layout=True)
+
+        else:
+            num_eom_plots = len(axes) - num_plots
 
         axes = np.asarray(axes).ravel()
 
-        axes[0].plot(con_nodes, state_violations.T)
-        axes[0].set_title('Constraint violations')
-        axes[0].set_xlabel('Node Number')
-        axes[0].set_ylabel('EoM violation')
+        if detailed_eoms == False or self.collocator.num_states == 1:
+            axes[0].plot(con_nodes, state_violations.T)
+            axes[0].set_title('Constraint violations')
+            axes[0].set_xlabel('Node Number')
+            axes[0].set_ylabel('EoM violation')
+
+        else:
+            for i in range(self.collocator.num_states):
+                k = i + 1
+                if k in (11,12,13):
+                    msg = 'th'
+                elif k % 10 == 1:
+                    msg = 'st'
+                elif k % 10 == 2:
+                    msg = 'nd'
+                elif k % 10 == 3:
+                    msg = 'rd'
+                else:
+                    msg = 'th'
path: opty/direct_collocation.py
user: login: Peter230655
body: > What is this elaborate if statement? It looks like something that would be prone to fail for arbitrary inputs.

Just optics: I did not want to write 1th, 2th 22th, etc. Taken care of with your suggestion to write Eq 1, Eq 2, etc.
created_at: 2025-03-21T07:49:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -448,6 +448,12 @@ def plot_constraint_violations(self, vector, axes=None):
             The initial guess, solution, or any other vector that is in the
             canonical form.
 
+        detailed_eoms : boolean, optional. If True, the equations of motion
+            will be plotted in a separate plot for each state. Default is False.
+
+        axes : ndarray of AxesSubplot, optional. If given, it is the user's
+            responsibility to provide the correct number of axes.
path: opty/direct_collocation.py
user: login: Peter230655
body: > You need to give a clue of what the number of subplots show be if subplots is true or false

created_at: 2025-03-21T07:54:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -448,6 +448,12 @@ def plot_constraint_violations(self, vector, axes=None):
             The initial guess, solution, or any other vector that is in the
             canonical form.
 
+        detailed_eoms : boolean, optional. If True, the equations of motion
+            will be plotted in a separate plot for each state. Default is False.
+
+        axes : ndarray of AxesSubplot, optional. If given, it is the user's
+            responsibility to provide the correct number of axes.
path: opty/direct_collocation.py
user: login: moorepants
body: Then write in the docstring that you should call the function once and count the subplots.
created_at: 2025-03-21T07:59:19Z
author_association: MEMBER
diff_hunk: @@ -448,6 +448,12 @@ def plot_constraint_violations(self, vector, axes=None):
             The initial guess, solution, or any other vector that is in the
             canonical form.
 
+        detailed_eoms : boolean, optional. If True, the equations of motion
+            will be plotted in a separate plot for each state. Default is False.
+
+        axes : ndarray of AxesSubplot, optional. If given, it is the user's
+            responsibility to provide the correct number of axes.
path: opty/direct_collocation.py
user: login: Peter230655
body: > Then write in the docstring that you should call the function once and count the subplots.

Will add this:


created_at: 2025-03-21T08:01:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -573,6 +574,22 @@ def plot_constraint_violations(self, vector, axes=None):
             The initial guess, solution, or any other vector that is in the
             canonical form.
 
+        subplots : boolean, optional.
+            If True, the equations of motion will be plotted in a separate
+            plot for each equation of motion.
+            Default is False.
+            If a user wants to provide the axes, it is recommended to run once
+            without providing axes, to see how many are needed.
+
+        figsize : float, optional.
path: opty/direct_collocation.py
user: login: moorepants
body: This is not needed because you can set it and pass in the axes before call this method.
created_at: 2025-03-22T08:42:11Z
author_association: MEMBER
diff_hunk: @@ -573,6 +574,22 @@ def plot_constraint_violations(self, vector, axes=None):
             The initial guess, solution, or any other vector that is in the
             canonical form.
 
+        subplots : boolean, optional.
+            If True, the equations of motion will be plotted in a separate
+            plot for each equation of motion.
+            Default is False.
+            If a user wants to provide the axes, it is recommended to run once
+            without providing axes, to see how many are needed.
+
+        figsize : float, optional.
path: opty/direct_collocation.py
user: login: Peter230655
body: > This is not needed because you can set it and pass in the axes before call this method.

Not sure what you mean. Do you mean lines 581, 582?
Or do you mena the kwarg figsize (maybe I should call it fig_height)
created_at: 2025-03-22T08:47:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -573,6 +574,22 @@ def plot_constraint_violations(self, vector, axes=None):
             The initial guess, solution, or any other vector that is in the
             canonical form.
 
+        subplots : boolean, optional.
+            If True, the equations of motion will be plotted in a separate
+            plot for each equation of motion.
+            Default is False.
+            If a user wants to provide the axes, it is recommended to run once
+            without providing axes, to see how many are needed.
+
+        figsize : float, optional.
path: opty/direct_collocation.py
user: login: moorepants
body: ```python
fig, ax = subplots(figsize=(2, 2))
problem.plot_something(ax=ax)
```
created_at: 2025-03-22T08:48:29Z
author_association: MEMBER
diff_hunk: @@ -573,6 +574,22 @@ def plot_constraint_violations(self, vector, axes=None):
             The initial guess, solution, or any other vector that is in the
             canonical form.
 
+        subplots : boolean, optional.
+            If True, the equations of motion will be plotted in a separate
+            plot for each equation of motion.
+            Default is False.
+            If a user wants to provide the axes, it is recommended to run once
+            without providing axes, to see how many are needed.
+
+        figsize : float, optional.
path: opty/direct_collocation.py
user: login: moorepants
body: matplotlib has a million settings, we are not in the business of customizing plots. our plots should make the most basic simple plot that doesn't look too awful. if a user wants to customize their plots they can pass in an axis or make their own plot.
created_at: 2025-03-22T08:49:46Z
author_association: MEMBER
diff_hunk: @@ -573,6 +574,22 @@ def plot_constraint_violations(self, vector, axes=None):
             The initial guess, solution, or any other vector that is in the
             canonical form.
 
+        subplots : boolean, optional.
+            If True, the equations of motion will be plotted in a separate
+            plot for each equation of motion.
+            Default is False.
+            If a user wants to provide the axes, it is recommended to run once
+            without providing axes, to see how many are needed.
+
+        figsize : float, optional.
path: opty/direct_collocation.py
user: login: Peter230655
body: > matplotlib has a million settings, we are not in the business of customizing plots. our plots should make the most basic simple plot that doesn't look too awful. if a user wants to customize their plots they can pass in an axis or make their own plot.

When I play around with matplotlib, I sometimes get the feel, your number above is way too low! :-)
created_at: 2025-03-22T09:06:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -573,6 +573,17 @@ def plot_constraint_violations(self, vector, axes=None):
             The initial guess, solution, or any other vector that is in the
             canonical form.
 
+        subplots : boolean, optional.
+            If True, the equations of motion will be plotted in a separate
+            plot for each equation of motion.
+            Default is False.
+            If a user wants to provide the axes, it is recommended to run once
+            without providing axes, to see how many are needed.
+
+        axes : ndarray of AxesSubplot, optional.
+            If given, it is the user's responsibility to provide the correct
+            number of axes.
path: opty/direct_collocation.py
user: login: moorepants
body: These are not in the same order as the function signature and there should not be an empty line between kwargs in the docstring.
created_at: 2025-03-22T09:08:01Z
author_association: MEMBER
diff_hunk: @@ -525,16 +531,43 @@ def plot_constraint_violations(self, vector, axes=None):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            fig, axes = plt.subplots(1 + num_plots, 1,
-                                     figsize=(6.4, 1.50*(1 + num_plots)),
-                                     layout='compressed')
+            if detailed_eoms == False or self.collocator.num_states == 1:
+                num_eom_plots = 1
+            else:
+                num_eom_plots = self.collocator.num_states
+            fig, axes = plt.subplots(num_eom_plots + num_plots, 1,
+                            figsize=(6.4, 1.75*(num_eom_plots + num_plots)),
+                            constrained_layout=True)
path: opty/direct_collocation.py
user: login: moorepants
body: I think the kwarg should be `layout='constrained'`.
created_at: 2025-03-22T09:08:43Z
author_association: MEMBER
diff_hunk: @@ -525,16 +531,43 @@ def plot_constraint_violations(self, vector, axes=None):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            fig, axes = plt.subplots(1 + num_plots, 1,
-                                     figsize=(6.4, 1.50*(1 + num_plots)),
-                                     layout='compressed')
+            if detailed_eoms == False or self.collocator.num_states == 1:
+                num_eom_plots = 1
+            else:
+                num_eom_plots = self.collocator.num_states
+            fig, axes = plt.subplots(num_eom_plots + num_plots, 1,
+                            figsize=(6.4, 1.75*(num_eom_plots + num_plots)),
+                            constrained_layout=True)
path: opty/direct_collocation.py
user: login: Peter230655
body: > I think the kwarg should be `layout='constrained'`.

I tried, it did not work for me, but I can try again.
created_at: 2025-03-22T09:10:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -525,16 +531,43 @@ def plot_constraint_violations(self, vector, axes=None):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            fig, axes = plt.subplots(1 + num_plots, 1,
-                                     figsize=(6.4, 1.50*(1 + num_plots)),
-                                     layout='compressed')
+            if detailed_eoms == False or self.collocator.num_states == 1:
+                num_eom_plots = 1
+            else:
+                num_eom_plots = self.collocator.num_states
+            fig, axes = plt.subplots(num_eom_plots + num_plots, 1,
+                            figsize=(6.4, 1.75*(num_eom_plots + num_plots)),
+                            constrained_layout=True)
path: opty/direct_collocation.py
user: login: Peter230655
body: > > I think the kwarg should be `layout='constrained'`.
> 
> I tried, it did not work for me, but I can try again.

Works the same way, it seems, so I changed to it.
created_at: 2025-03-22T09:20:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -525,16 +531,43 @@ def plot_constraint_violations(self, vector, axes=None):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            fig, axes = plt.subplots(1 + num_plots, 1,
-                                     figsize=(6.4, 1.50*(1 + num_plots)),
-                                     layout='compressed')
+            if detailed_eoms == False or self.collocator.num_states == 1:
+                num_eom_plots = 1
+            else:
+                num_eom_plots = self.collocator.num_states
+            fig, axes = plt.subplots(num_eom_plots + num_plots, 1,
+                            figsize=(6.4, 1.75*(num_eom_plots + num_plots)),
+                            constrained_layout=True)
path: opty/direct_collocation.py
user: login: moorepants
body: The official argument is explained here: https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.figure.html#matplotlib.pyplot.figure we should use what is the docs.
created_at: 2025-03-22T09:33:01Z
author_association: MEMBER
diff_hunk: @@ -525,16 +531,43 @@ def plot_constraint_violations(self, vector, axes=None):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            fig, axes = plt.subplots(1 + num_plots, 1,
-                                     figsize=(6.4, 1.50*(1 + num_plots)),
-                                     layout='compressed')
+            if detailed_eoms == False or self.collocator.num_states == 1:
+                num_eom_plots = 1
+            else:
+                num_eom_plots = self.collocator.num_states
+            fig, axes = plt.subplots(num_eom_plots + num_plots, 1,
+                            figsize=(6.4, 1.75*(num_eom_plots + num_plots)),
+                            constrained_layout=True)
path: opty/direct_collocation.py
user: login: Peter230655
body: > The official argument is explained here: https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.figure.html#matplotlib.pyplot.figure we should use what is the docs.

I likely got my ``constrained_layout=True`` from chatGPT, I do not recall. I changed anyway.
created_at: 2025-03-22T09:37:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -604,7 +602,7 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False):
         bars_per_plot = None
         rotation = -45
 
-        if subplots == False:
+        if subplots:
path: opty/direct_collocation.py
user: login: Peter230655
body: Did you not replace the original condition with itsa opposite?
created_at: 2025-03-22T10:12:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -668,22 +666,22 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            if subplots == False or self.collocator.num_states == 1:
+            if subplots or self.collocator.num_states == 1:
path: opty/direct_collocation.py
user: login: Peter230655
body: Did you not replace the original condition with its opposite?
created_at: 2025-03-22T10:13:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -668,22 +666,22 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            if subplots == False or self.collocator.num_states == 1:
+            if subplots or self.collocator.num_states == 1:
                 num_eom_plots = 1
             else:
                 num_eom_plots = self.collocator.num_states
 
             fig, axes = plt.subplots(num_eom_plots + num_plots, 1,
-                            figsize=(6.4, figsize*(num_eom_plots + num_plots)),
-                            layout='constrained')
-        #                    constrained_layout=True)
+                                     figsize=(6.4, figsize*(num_eom_plots +
+                                                            num_plots)),
+                                     layout='constrained')
 
         else:
             num_eom_plots = len(axes) - num_plots
 
         axes = np.asarray(axes).ravel()
 
-        if subplots == False or self.collocator.num_states == 1:
+        if subplots or self.collocator.num_states == 1:
path: opty/direct_collocation.py
user: login: Peter230655
body: Didyou not replace the original condition with its opposite?
created_at: 2025-03-22T10:16:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -668,22 +666,22 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            if subplots == False or self.collocator.num_states == 1:
+            if subplots or self.collocator.num_states == 1:
path: opty/direct_collocation.py
user: login: moorepants
body: oop, yes, it should be `is False` not `== False`
created_at: 2025-03-22T10:16:50Z
author_association: MEMBER
diff_hunk: @@ -668,22 +666,22 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False):
         con_nodes = range(1, self.collocator.num_collocation_nodes)
 
         if axes is None:
-            if subplots == False or self.collocator.num_states == 1:
+            if subplots or self.collocator.num_states == 1:
path: opty/direct_collocation.py
user: login: Peter230655
body: > oop, yes, it should be `is False` not `== False`

Is ``is False`` more pythonic than ``== False``?
created_at: 2025-03-22T10:19:55Z
author_association: CONTRIBUTOR

PR comments:
user: login: moorepants
created_at: 2025-02-01T06:25:37Z
body: This puts each equation on a new row:
![image](https://github.com/user-attachments/assets/d2eef1b3-a09b-4d3a-9571-35d2e6180d28)

Note that we switched to the single axis deliberately here: https://github.com/csu-hmc/opty/pull/125
author_association: MEMBER
user: login: moorepants
created_at: 2025-02-01T06:30:16Z
body: In general, one is needing to know if any constraints are violated *at all*. What is the reason to make the default plot show the relative differences in the equation of motion violations?

Secondly , have you tested this with very large # of EoMs? Does it always make a plot that isn't extremely squished vertically?

I prefer that the plot functions just work and have few to no knobs to turn. Anyone can create their own plotting functions or manipulate the returned axes. I'd like these to give a default average plot to quickly view with simple function calls.
author_association: MEMBER
user: login: Peter230655
created_at: 2025-02-01T07:15:28Z
body: > In general, one is needing to know if any constraints are violated _at all_. What is the reason to make the default plot show the relative differences in the equation of motion violations?
> 

As I remember, with some simulation I wanted to see which eom was violated severely, but I must admit I do not remember exactly. I just thought it does not hurt having it.

> Secondly , have you tested this with very large # of EoMs? Does it always make a plot that isn't extremely squished vertically?

I tested it with many of my simulations, and never noticed this. Max number maybe 15 or 20 probably not more.

> 
> I prefer that the plot functions just work and have few to no knobs to turn. Anyone can create their own plotting functions or manipulate the returned axes. I'd like these to give a default average plot to quickly view with simple function calls.

The optional detailed_eoms is set to false. So, the knob need not be turned. Then everything is exactly like the current version.


author_association: CONTRIBUTOR
user: login: Peter230655
created_at: 2025-02-01T11:55:53Z
body: For info:
The drone example in examples-gallery has 14 eoms.
Detailed printout looks like this:
![image](https://github.com/user-attachments/assets/bf0c47bf-7d0a-488f-b3dc-9e10dad53f4d)

at the bottom it looks like this: (of course the shifting to the left is only because I had to take two screen shots)
![image](https://github.com/user-attachments/assets/eda460b7-ecbb-470b-9e1b-db28bcf8a861)


author_association: CONTRIBUTOR
user: login: Peter230655
created_at: 2025-02-01T16:22:28Z
body: > This puts each equation on a new row: ![image](https://private-user-images.githubusercontent.com/276007/408779929-d2eef1b3-a09b-4d3a-9571-35d2e6180d28.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzg0MjcxMzUsIm5iZiI6MTczODQyNjgzNSwicGF0aCI6Ii8yNzYwMDcvNDA4Nzc5OTI5LWQyZWVmMWIzLWEwOWItNGQzYS05NTcxLTM1ZDJlNjE4MGQyOC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMjAxJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDIwMVQxNjIwMzVaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT04ODUyMGJkYjZkNThiY2FjOTYzODU4NWQzNWYxNDFlNTEyYTUwZGQwZTI5ZWZkMTJhMDRjZjRlNTI0Zjk4NmJmJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.fiI5Yu_pGIOYP3GT9bK_vDM9KzhBlBJkI9y-Z3QpzAM)
> 
> Note that we switched to the single axis deliberately here: #125

This only happens if the eoms should be printed individually, otherwise nothng changes.
What I am saying now is **not** a proof, just an observation:
Since I made this PR for detailed eoms, I must have run 50 or more simulations with detailed eoms and without - and never had a problem except that in rare cases (I forgot what caused it) the rounding of the errors in the instance constraints did not work. I pushed a simple PR to fix it.    


author_association: CONTRIBUTOR
user: login: Peter230655
created_at: 2025-03-21T06:40:04Z
body: > > This puts each equation on a new row: ![image](https://private-user-images.githubusercontent.com/276007/408779929-d2eef1b3-a09b-4d3a-9571-35d2e6180d28.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzg0MjcxMzUsIm5iZiI6MTczODQyNjgzNSwicGF0aCI6Ii8yNzYwMDcvNDA4Nzc5OTI5LWQyZWVmMWIzLWEwOWItNGQzYS05NTcxLTM1ZDJlNjE4MGQyOC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMjAxJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDIwMVQxNjIwMzVaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT04ODUyMGJkYjZkNThiY2FjOTYzODU4NWQzNWYxNDFlNTEyYTUwZGQwZTI5ZWZkMTJhMDRjZjRlNTI0Zjk4NmJmJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.fiI5Yu_pGIOYP3GT9bK_vDM9KzhBlBJkI9y-Z3QpzAM)
> > Note that we switched to the single axis deliberately here: #125

But they are related to the eoms, so one can see the violation of each eom?
I label them like ``EOM 1 violation, EOM 2 violation``, etc. Could this not help on occasion, to see if a configuration constraint is the one that is violated the worst?
> 
> This only happens if the eoms should be printed individually, otherwise nothng changes. What I am saying now is **not** a proof, just an observation: Since I made this PR for detailed eoms, I must have run 50 or more simulations with detailed eoms and without - and never had a problem except that in rare cases (I forgot what caused it) the rounding of the errors in the instance constraints did not work. I pushed a simple PR to fix it.


author_association: CONTRIBUTOR
user: login: moorepants
created_at: 2025-03-21T07:18:58Z
body: Can you demonstrate that this works when the # eoms is large and the # of instance constraints is large (together)?
author_association: MEMBER
user: login: Peter230655
created_at: 2025-03-21T07:21:42Z
body: > Can you demonstrate that this works when the # eoms is large and the # of instance constraints is large (together)?

I am not sure, I understand. Do you mean, if we have, say, 35 eoms, the plots will look ugly?
author_association: CONTRIBUTOR
user: login: moorepants
created_at: 2025-03-21T07:22:55Z
body: Yes, you can find the example in our gallery that has the most eoms and most instance constraints and show what it looks like.
author_association: MEMBER
user: login: Peter230655
created_at: 2025-03-21T07:24:43Z
body: > Yes, you can find the example in our gallery that has the most eoms and most instance constraints and show what it looks like.

I used your drone, which  has 14 eoms. It worked, see above. 
author_association: CONTRIBUTOR
user: login: moorepants
created_at: 2025-03-21T07:42:43Z
body: I only see one row of instance constraints in what you show above. We need to prove it works for many eoms + many rows of instance constraints.
author_association: MEMBER
user: login: moorepants
created_at: 2025-03-21T07:42:59Z
body: please show the entire plot
author_association: MEMBER
user: login: Peter230655
created_at: 2025-03-21T07:47:54Z
body: > I only see one row of instance constraints in what you show above. We need to prove it works for many eoms + many rows of instance constraints.

I will find a suitable example, I hope.
author_association: CONTRIBUTOR
user: login: moorepants
created_at: 2025-03-21T08:15:30Z
body: Wouldn't the gait example have at least two rows of instance constraints?
author_association: MEMBER
user: login: Peter230655
created_at: 2025-03-21T08:18:18Z
body: > Wouldn't the gait example have at least two rows of instance constraints?



> Wouldn't the gait example have at least two rows of instance constraints?

I will have to look. 
author_association: CONTRIBUTOR
user: login: moorepants
created_at: 2025-03-21T09:37:04Z
body: The thing is we don't write unit tests for the plots, so if we don't check them thoroughly manually, we may have edge cases.
author_association: MEMBER
user: login: Peter230655
created_at: 2025-03-21T20:48:45Z
body: A first attempt. 
[plot_sit_to_stand.pdf](https://github.com/user-attachments/files/19397104/plot_sit_to_stand.pdf)
You got a much better feel for aestetics.

(I was surprised badly some eoms are violated)
author_association: CONTRIBUTOR
user: login: moorepants
created_at: 2025-03-22T02:11:42Z
body: That show it works, even for large sets of constraints. Thanks for checking.
author_association: MEMBER
user: login: Peter230655
created_at: 2025-03-22T04:13:48Z
body: I will test more simulations. Aestetics o.k.?
Thanks!
author_association: CONTRIBUTOR
user: login: moorepants
created_at: 2025-03-22T04:29:01Z
body: What you have shown is sufficient, as it is the worst case scenario.
author_association: MEMBER
user: login: Peter230655
created_at: 2025-03-22T05:18:52Z
body: > What you have shown is sufficient, as it is the worst case scenario.

Thanls!
Does it **look** o.k. in your opinion? I mean the size of the plots, etc.
author_association: CONTRIBUTOR
user: login: Peter230655
created_at: 2025-03-22T08:03:49Z
body: I tested on many 15 - 20 excamples in examples-gallery.
[plot_sit_to_stand_subplots=False.pdf](https://github.com/user-attachments/files/19401524/plot_sit_to_stand_subplots.False.pdf)
[plot_sit_to_stand_subplots=True.pdf](https://github.com/user-attachments/files/19401525/plot_sit_to_stand_subplots.True.pdf)

I noticed, that figsize = 1.25 worked fine with subplots=True, but gave sometimes 'ugly' plots with it.
figsize = 1.75 works fine with subplots=False, but too large for subplots=True.
So, I set it accordingly.

I added a quark ``figsize`` to give the use additional flexibility.
The figsizes I have preset (1.25 / 1.75) worked fine for all my tests.

author_association: CONTRIBUTOR
user: login: Peter230655
created_at: 2025-03-22T08:18:50Z
body: I am snsure about the error:
I says a problem with ``pprint(par_map)`` in plot_sit_to_stand.py and in plot_human_gait.py, but I did only changed the method``plot_constraint_violations(..)``
author_association: CONTRIBUTOR
user: login: moorepants
created_at: 2025-03-22T08:34:47Z
body: The height of the subplot is mostly governed by how long you make the y label.
author_association: MEMBER
user: login: Peter230655
created_at: 2025-03-22T08:38:04Z
body: > The height of the subplot is mostly governed by how long you make the y label.

I shortend the y - labels when showing distict eom plots.
After trials, what I implemented gave nice plots in all my tests.
author_association: CONTRIBUTOR
user: login: moorepants
created_at: 2025-03-22T08:42:57Z
body: update with master to see if it fixes the error
author_association: MEMBER
user: login: Peter230655
created_at: 2025-03-22T09:03:15Z
body: My origin was up-to-date
I did
- pull origin master. It said my master already was up-to-date
- detailed-eoms-1 merge master. I said already up-to-date

Removed ``figsize``. As per my tests not really needed.
author_association: CONTRIBUTOR
user: login: moorepants
created_at: 2025-03-22T09:06:50Z
body: > detailed-eoms-1 merge master. I said already up-to-date

I don't see how that is possible as there are no merge commits here in this branch.
author_association: MEMBER
user: login: Peter230655
created_at: 2025-03-22T09:27:09Z
body: > > detailed-eoms-1 merge master. I said already up-to-date
> 
> I don't see how that is possible as there are no merge commits here in this branch.

I did it again, same results.
Maybe easiest to close this PR and do a new one?
If it says: ``no conflict with base branch`` should it not be o..k then?

author_association: CONTRIBUTOR
user: login: moorepants
created_at: 2025-03-22T09:33:29Z
body: >  Maybe easiest to close this PR and do a new one?

No, we figure out what is wrong here.
author_association: MEMBER
user: login: Peter230655
created_at: 2025-03-22T09:34:25Z
body: > > Maybe easiest to close this PR and do a new one?
> 
> No, we figure out what is wrong here.

This way, I will earn something for sure! Just more burden on you!
I did my tests, as you saw, also with ``plot_sit_to_stand.py`` and obviously it worked.
author_association: CONTRIBUTOR
user: login: moorepants
created_at: 2025-03-22T09:47:13Z
body: I did this in pygait2d yesterday: https://github.com/csu-hmc/gait2d/commit/1b86c50154fcc73a206b3f55900f4272365f6b90 it must be causing an issue.
author_association: MEMBER
user: login: Peter230655
created_at: 2025-03-22T09:54:37Z
body: > I did this in pygait2d yesterday: [csu-hmc/gait2d@1b86c50](https://github.com/csu-hmc/gait2d/commit/1b86c50154fcc73a206b3f55900f4272365f6b90) it must be causing an issue.

...but if this was merged, I should experience the same problem?
author_association: CONTRIBUTOR
user: login: moorepants
created_at: 2025-03-22T09:59:16Z
body: Not if you haven't updated gait2d.
author_association: MEMBER
user: login: Peter230655
created_at: 2025-03-22T11:10:39Z
body: Thanks! Sorry it caused so much work for you!
author_association: CONTRIBUTOR