From 4e358417620888e331e591fadf1c9da2a6881edc Mon Sep 17 00:00:00 2001 From: Peter Stahlecker Date: Thu, 8 May 2025 14:19:24 +0200 Subject: [PATCH 1/6] nothing --- opty/direct_collocation.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/opty/direct_collocation.py b/opty/direct_collocation.py index 98eb65ab..bab690eb 100644 --- a/opty/direct_collocation.py +++ b/opty/direct_collocation.py @@ -716,11 +716,22 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False): instance_violations = con_violations[len(eom_violations):] eom_violations = eom_violations.reshape((self.collocator.num_eom, N - 1)) - # TODO : figure out a way to plot the inequality constraint violations - # don't plot inequality - if self.eom_bounds is not None: - for k, v in self.eom_bounds.items(): - eom_violations[k] = np.nan + # If an eom has bounds a < eom < b and if subplots is False, this will + # be displayed for each node: + # eom - a if eom < a + # eom - b if eom > b + # zero otherwise. + if self.eom_bounds is not None and not subplots: + for k in self.eom_bounds.keys(): + left = self.eom_bounds[k][0] + right = self.eom_bounds[k][1] + for i in range(N - 1): + if eom_violations[k, i] < left: + eom_violations[k, i] = eom_violations[k, i] - left + elif eom_violations[k, i] > right: + eom_violations[k, i] = eom_violations[k, i] - right + else: + eom_violations[k, i] = 0.0 con_nodes = range(1, self.collocator.num_collocation_nodes) @@ -749,10 +760,16 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False): else: for i in range(self.collocator.num_eom): if ((self.eom_bounds is not None) and - (i in self.eom_bounds.keys())): # don't plot if inequality - axes[i].plot(con_nodes, np.nan*np.ones_like(con_nodes)) - axes[i].set_ylabel(f'Eq. {str(i+1)} \n not shown', + (i in self.eom_bounds.keys())): + axes[i].plot(con_nodes, eom_violations[i]) + axes[i].set_ylabel(f'Eq. {str(i+1)} \n value', fontsize=9) + axes[i].axhline(self.eom_bounds[i][0], color='C1', lw=1.0, + linestyle='--') + axes[i].axhline(self.eom_bounds[i][1], color='C1', lw=1.0, + linestyle='--') + + else: axes[i].plot(con_nodes, eom_violations[i]) axes[i].set_ylabel(f'Eq. {str(i+1)} \n violation', @@ -760,6 +777,11 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False): if i < self.collocator.num_eom - 1: axes[i].set_xticklabels([]) axes[num_eom_plots-1].set_xlabel('Node Number') + if self.eom_bounds is None: + axes[num_eom_plots-1].set_title('Constraint violations') + else: + axes[num_eom_plots-1].set_title((f'Constraint violations \n' + f'value of bounded eoms')) axes[0].set_title('Constraint violations') if self.collocator.instance_constraints is not None: From 052452e21209c3c83efe0daa5d3fe8e2f551c114 Mon Sep 17 00:00:00 2001 From: Peter Stahlecker Date: Thu, 8 May 2025 15:33:32 +0200 Subject: [PATCH 2/6] plot constraint violations adapted to eom_bounds. Fixes #448 --- opty/direct_collocation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opty/direct_collocation.py b/opty/direct_collocation.py index bab690eb..6084043b 100644 --- a/opty/direct_collocation.py +++ b/opty/direct_collocation.py @@ -762,7 +762,7 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False): if ((self.eom_bounds is not None) and (i in self.eom_bounds.keys())): axes[i].plot(con_nodes, eom_violations[i]) - axes[i].set_ylabel(f'Eq. {str(i+1)} \n value', + axes[i].set_ylabel(f'Eq. {str(i)} \n value', fontsize=9) axes[i].axhline(self.eom_bounds[i][0], color='C1', lw=1.0, linestyle='--') @@ -772,7 +772,7 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False): else: axes[i].plot(con_nodes, eom_violations[i]) - axes[i].set_ylabel(f'Eq. {str(i+1)} \n violation', + axes[i].set_ylabel(f'Eq. {str(i)} \n violation', fontsize=9) if i < self.collocator.num_eom - 1: axes[i].set_xticklabels([]) From ec44a52d0ce6bd4283d4d3ddd2633731b1e517ec Mon Sep 17 00:00:00 2001 From: Peter Stahlecker Date: Thu, 8 May 2025 16:04:42 +0200 Subject: [PATCH 3/6] corrected headings --- opty/direct_collocation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/opty/direct_collocation.py b/opty/direct_collocation.py index 6084043b..eb924135 100644 --- a/opty/direct_collocation.py +++ b/opty/direct_collocation.py @@ -778,11 +778,11 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False): axes[i].set_xticklabels([]) axes[num_eom_plots-1].set_xlabel('Node Number') if self.eom_bounds is None: - axes[num_eom_plots-1].set_title('Constraint violations') + axes[0].set_title('Constraint violations') else: - axes[num_eom_plots-1].set_title((f'Constraint violations \n' - f'value of bounded eoms')) - axes[0].set_title('Constraint violations') + axes[0].set_title((f'Constraint violations \n' + f'Values of bounde EOMs')) + if self.collocator.instance_constraints is not None: # reduce the instance constraints to 2 digits after the decimal From c936bb2150f477fd51cf85586c9df822891c7447 Mon Sep 17 00:00:00 2001 From: Peter Stahlecker Date: Fri, 9 May 2025 11:22:43 +0200 Subject: [PATCH 4/6] added single EOM, explanation in docstrings --- opty/direct_collocation.py | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/opty/direct_collocation.py b/opty/direct_collocation.py index eb924135..ca71737f 100644 --- a/opty/direct_collocation.py +++ b/opty/direct_collocation.py @@ -651,7 +651,24 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False): - r : number of unknown parameters - s : number of unknown time intervals + If ``eom_bounds`` are given as :math:`a \leq eom \leq b` and + ``subplots = True``, the values of the respective eoms are plotted and + their bounds are shown as dashed lines. + + if ``eom_bounds`` are given and ``subplots = False``, the eom + violations are plotted. The violations are calculated as follows: + + - eom - a if eom < a + - eom - b if eom > b + - 0 otherwise. + + If only one eom is given and and eom bounds are given, then + ``subplots`` is set to ``True``. + """ + # This is done to make the cases below simpler. + if self.collocator.num_eom == 1: + subplots = True bars_per_plot = None rotation = -45 @@ -753,9 +770,22 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False): if subplots is False or self.collocator.num_eom == 1: axes[0].plot(con_nodes, eom_violations.T) - axes[0].set_title('Constraint violations') - axes[0].set_xlabel('Node Number') - axes[0].set_ylabel('EoM violation') + if self.eom_bounds is None: + axes[0].set_title('Constraint violations') + axes[0].set_xlabel('Node Number') + axes[0].set_ylabel('EoM violation') + elif self.collocator.num_eom == 1: + axes[0].set_title('Constraint violations') + axes[0].set_xlabel('Node Number') + axes[0].set_ylabel('EoM value') + axes[0].axhline(self.eom_bounds[0][0], color='C1', lw=1.0, + linestyle='--') + axes[0].axhline(self.eom_bounds[0][1], color='C1', lw=1.0, + linestyle='--') + else: + axes[0].set_title('Constraint violations') + axes[0].set_xlabel('Node Number') + axes[0].set_ylabel('EoM violations') else: for i in range(self.collocator.num_eom): @@ -781,7 +811,7 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False): axes[0].set_title('Constraint violations') else: axes[0].set_title((f'Constraint violations \n' - f'Values of bounde EOMs')) + f'Values of bounded EoMs')) if self.collocator.instance_constraints is not None: From 2b03a90bc3dbf9aaeba1e71bbbe0ccaf8227c36b Mon Sep 17 00:00:00 2001 From: Peter Stahlecker Date: Thu, 12 Jun 2025 18:28:56 +0200 Subject: [PATCH 5/6] single eom still to be corrected --- opty/direct_collocation.py | 54 +++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/opty/direct_collocation.py b/opty/direct_collocation.py index 9018f3ea..b49be21e 100644 --- a/opty/direct_collocation.py +++ b/opty/direct_collocation.py @@ -627,7 +627,8 @@ def plot_trajectories(self, vector, axes=None, show_bounds=False): return axes @_optional_plt_dep - def plot_constraint_violations(self, vector, axes=None, subplots=False): + def plot_constraint_violations(self, vector, axes=None, subplots=False, + show_range=False): """Returns an axis with the state constraint violations plotted versus node number and the instance constraints as a bar graph. @@ -644,6 +645,11 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False): 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. + show_range : boolean, optional. + If True and if ``eom_bounds`` are given, and if + ``subplots`` is True the range of the bounded equations of motion + will be shown. Otherwise the violations of the bounds will be + shown. Default is False. Returns ======= @@ -663,8 +669,8 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False): - s : number of unknown time intervals If ``eom_bounds`` are given as :math:`a \leq eom \leq b` and - ``subplots = True``, the values of the respective eoms are plotted and - their bounds are shown as dashed lines. + ``subplots = True``, and ``show_range`` is True the values of the + respective eoms are plotted and their bounds are shown as dashed lines. if ``eom_bounds`` are given and ``subplots = False``, the eom violations are plotted. The violations are calculated as follows: @@ -798,7 +804,7 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False): axes[0].set_xlabel('Node Number') axes[0].set_ylabel('EoM violations') - else: + elif subplots is True and show_range is True: for i in range(self.collocator.num_eom): if ((self.eom_bounds is not None) and (i in self.eom_bounds.keys())): @@ -824,6 +830,46 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False): axes[0].set_title((f'Constraint violations \n' f'Values of bounded EoMs')) + elif subplots is True and show_range is False: + for i in range(self.collocator.num_eom): + if ((self.eom_bounds is not None) and + (i in self.eom_bounds.keys())): + + left = self.eom_bounds[i][0] + right = self.eom_bounds[i][1] + for ii in range(N - 1): + if eom_violations[i, ii] < left: + eom_violations[i, ii] = (eom_violations[i, ii] - + left) + elif eom_violations[i, ii] > right: + eom_violations[i, ii] = (eom_violations[i, ii] - + right) + else: + eom_violations[i, ii] = 0.0 + axes[i].plot(con_nodes, eom_violations[i]) + axes[i].set_ylabel(f'Eq. {str(i)} \n violation', + fontsize=9) + #axes[i].axhline(self.eom_bounds[i][0], color='C1', lw=1.0, + # linestyle='--') + #axes[i].axhline(self.eom_bounds[i][1], color='C1', lw=1.0, + # linestyle='--') + + + else: + axes[i].plot(con_nodes, eom_violations[i]) + axes[i].set_ylabel(f'Eq. {str(i)} \n violation', + fontsize=9) + if i < self.collocator.num_eom - 1: + axes[i].set_xticklabels([]) + axes[num_eom_plots-1].set_xlabel('Node Number') + if self.eom_bounds is None: + axes[0].set_title('Constraint violations') + else: + axes[0].set_title((f'Constraint violations \n' + f'Values of bounded EoMs')) + else: + raise ValueError('Something went wrong with the subplots.') + if self.collocator.instance_constraints is not None: # reduce the instance constraints to 2 digits after the decimal From 2550934bb30478c38e740fcab5b5d887cc756559 Mon Sep 17 00:00:00 2001 From: Peter Stahlecker Date: Sat, 14 Jun 2025 06:39:52 +0200 Subject: [PATCH 6/6] changed as per hackathon discussion but added keyword --- opty/direct_collocation.py | 77 ++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 44 deletions(-) diff --git a/opty/direct_collocation.py b/opty/direct_collocation.py index b49be21e..dfd07a1a 100644 --- a/opty/direct_collocation.py +++ b/opty/direct_collocation.py @@ -650,6 +650,9 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False, ``subplots`` is True the range of the bounded equations of motion will be shown. Otherwise the violations of the bounds will be shown. Default is False. + If number of equations of motion is larger than one and subplots is + False, only the violations are plotted, regardless of the value + of ``show_range``. Returns ======= @@ -679,21 +682,14 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False, - eom - b if eom > b - 0 otherwise. - If only one eom is given and and eom bounds are given, then - ``subplots`` is set to ``True``. - """ - # This is done to make the cases below simpler. - if self.collocator.num_eom == 1: - subplots = True - bars_per_plot = None rotation = -45 - if subplots: - figsize = 1.25 - else: + if subplots is False or self.collocator.num_eom == 1: figsize = 1.75 + else: + figsize = 1.25 if not isinstance(figsize, float): raise ValueError('figsize given must be a float.') @@ -750,23 +746,6 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False, instance_violations = con_violations[len(eom_violations):] eom_violations = eom_violations.reshape((self.collocator.num_eom, N - 1)) - # If an eom has bounds a < eom < b and if subplots is False, this will - # be displayed for each node: - # eom - a if eom < a - # eom - b if eom > b - # zero otherwise. - if self.eom_bounds is not None and not subplots: - for k in self.eom_bounds.keys(): - left = self.eom_bounds[k][0] - right = self.eom_bounds[k][1] - for i in range(N - 1): - if eom_violations[k, i] < left: - eom_violations[k, i] = eom_violations[k, i] - left - elif eom_violations[k, i] > right: - eom_violations[k, i] = eom_violations[k, i] - right - else: - eom_violations[k, i] = 0.0 - con_nodes = range(1, self.collocator.num_collocation_nodes) if axes is None: @@ -784,25 +763,44 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False, num_eom_plots = len(axes) - num_plots axes = np.asarray(axes).ravel() - if subplots is False or self.collocator.num_eom == 1: - axes[0].plot(con_nodes, eom_violations.T) if self.eom_bounds is None: + axes[0].plot(con_nodes, eom_violations.T) axes[0].set_title('Constraint violations') axes[0].set_xlabel('Node Number') axes[0].set_ylabel('EoM violation') - elif self.collocator.num_eom == 1: - axes[0].set_title('Constraint violations') + elif self.collocator.num_eom == 1 and show_range is True: + axes[0].plot(con_nodes, eom_violations[0]) + axes[0].set_title('Value of Bounded EoM') axes[0].set_xlabel('Node Number') axes[0].set_ylabel('EoM value') axes[0].axhline(self.eom_bounds[0][0], color='C1', lw=1.0, linestyle='--') axes[0].axhline(self.eom_bounds[0][1], color='C1', lw=1.0, linestyle='--') + # if subplots is False and more than one EoM is present, only the + # violations are plotted, not the values of the EoMs, rwgardless of + # the value of show_range. else: - axes[0].set_title('Constraint violations') + print('here we are') + for i in range(self.collocator.num_eom): + if i in self.eom_bounds.keys(): + left = self.eom_bounds[i][0] + right = self.eom_bounds[i][1] + for ii in range(N - 1): + if eom_violations[i, ii] < left: + eom_violations[i, ii] = (eom_violations[i, ii] + - left) + elif eom_violations[i, ii] > right: + eom_violations[i, ii] = (eom_violations[i, ii] + - right) + else: + eom_violations[i, ii] = 0.0 + axes[0].plot(con_nodes, eom_violations.T) + axes[0].set_ylabel('EoM violation', fontsize=9) axes[0].set_xlabel('Node Number') - axes[0].set_ylabel('EoM violations') + axes[0].set_title('Constraint violations') + elif subplots is True and show_range is True: for i in range(self.collocator.num_eom): @@ -849,11 +847,6 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False, axes[i].plot(con_nodes, eom_violations[i]) axes[i].set_ylabel(f'Eq. {str(i)} \n violation', fontsize=9) - #axes[i].axhline(self.eom_bounds[i][0], color='C1', lw=1.0, - # linestyle='--') - #axes[i].axhline(self.eom_bounds[i][1], color='C1', lw=1.0, - # linestyle='--') - else: axes[i].plot(con_nodes, eom_violations[i]) @@ -862,13 +855,9 @@ def plot_constraint_violations(self, vector, axes=None, subplots=False, if i < self.collocator.num_eom - 1: axes[i].set_xticklabels([]) axes[num_eom_plots-1].set_xlabel('Node Number') - if self.eom_bounds is None: - axes[0].set_title('Constraint violations') - else: - axes[0].set_title((f'Constraint violations \n' - f'Values of bounded EoMs')) + axes[0].set_title('Constraint violations') else: - raise ValueError('Something went wrong with the subplots.') + raise ValueError('Something wrong with EOM constraints.') if self.collocator.instance_constraints is not None: