|
From: <mur...@us...> - 2011-07-25 05:00:48
|
Revision: 169
http://python-control.svn.sourceforge.net/python-control/?rev=169&view=rev
Author: murrayrm
Date: 2011-07-25 05:00:41 +0000 (Mon, 25 Jul 2011)
Log Message:
-----------
* Added first cut at phase portrait command (PhasePlot) from MATLAB
code; needs more work to pythonize the call signatures
* Added simple unit tests for margin command
See ChangeLog for more detailed list of changes
Modified Paths:
--------------
trunk/ChangeLog
Added Paths:
-----------
trunk/examples/README
trunk/examples/phaseplots.py
trunk/src/phaseplot.py
trunk/tests/margin_test.py
trunk/tests/phaseplot_test.py
Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog 2011-07-16 06:05:46 UTC (rev 168)
+++ trunk/ChangeLog 2011-07-25 05:00:41 UTC (rev 169)
@@ -1,3 +1,17 @@
+2011-07-24 Richard Murray <mu...@ma...>
+
+ * tests/margin_test.py: added simple unit tests for margin functions
+ (initial versions just call functions; some comparisons missing)
+
+ * examples/README: added missing README file
+
+ * examples/phaseplots.py: FBS examples for phaseplot
+
+ * tests/phaseplot_test.py: unit tests for phaseplot
+
+ * src/phaseplot.py: initial cut at phase portrait function, built
+ from amphaseplot (Feeback Systems [FBS], Astrom and Murray, 2008)
+
2011-07-15 Richard Murray <mu...@ma...>
* tests/matlab_test.py (TestMatlab): added unittest for margin()
Added: trunk/examples/README
===================================================================
--- trunk/examples/README (rev 0)
+++ trunk/examples/README 2011-07-25 05:00:41 UTC (rev 169)
@@ -0,0 +1,4 @@
+This directory contains worked examples using the python-control
+library. Each example should work by running 'ipython -pylab' and
+then running the given file (make sure to have the python-control
+module in your path).
Added: trunk/examples/phaseplots.py
===================================================================
--- trunk/examples/phaseplots.py (rev 0)
+++ trunk/examples/phaseplots.py 2011-07-25 05:00:41 UTC (rev 169)
@@ -0,0 +1,120 @@
+# phaseplots.py - examples of phase portraits
+# RMM, 24 July 2011
+#
+# This file contains examples of phase portraits pulled from "Feedback
+# Systems" by Astrom and Murray (Princeton University Press, 2008).
+
+import numpy as np
+import matplotlib.pyplot as mpl
+from control.phaseplot import PhasePlot
+from numpy import pi
+
+# Clear out any figures that are present
+mpl.close('all')
+
+#
+# Inverted pendulum
+#
+
+# Define the ODEs for a damped (inverted) pendulum
+def invpend_ode(x, t, m=1., l=1., b=0.2, g=1):
+ return (x[1], -b/m*x[1] + (g*l/m) * np.sin(x[0]))
+
+# Set up the figure the way we want it to look
+mpl.figure(); mpl.clf();
+mpl.axis([-2*pi, 2*pi, -2.1, 2.1]);
+mpl.title('Inverted pendlum')
+
+# Outer trajectories
+PhasePlot(invpend_ode,
+ 'logtime', (3, 0.7), None,
+ [ [-2*pi, 1.6], [-2*pi, 0.5], [-1.8, 2.1],
+ [-1, 2.1], [4.2, 2.1], [5, 2.1],
+ [2*pi, -1.6], [2*pi, -0.5], [1.8, -2.1],
+ [1, -2.1], [-4.2, -2.1], [-5, -2.1] ],
+ np.linspace(0, 40, 200))
+
+# Separatrices
+mpl.hold(True);
+PhasePlot(invpend_ode, 'auto', 0, None, [[-2.3056, 2.1], [2.3056, -2.1]], 6)
+mpl.show();
+
+#
+# Systems of ODEs: damped oscillator example (simulation + phase portrait)
+#
+
+def oscillator_ode(x, t, m=1., b=1, k=1):
+ return (x[1], -k/m*x[0] - b/m*x[1])
+
+# Generate a vector plot for the damped oscillator
+mpl.figure(); mpl.clf();
+PhasePlot(oscillator_ode, [-1, 1, 10], [-1, 1, 10], 0.15);
+mpl.hold(True); mpl.plot([0], [0], '.');
+# a=gca; set(a,'FontSize',20); set(a,'DataAspectRatio',[1,1,1]);
+mpl.xlabel('x1'); mpl.ylabel('x2');
+
+# Generate a phase plot for the damped oscillator
+mpl.figure(); mpl.clf();
+mpl.axis([-1, 1, -1, 1]); # set(gca, 'DataAspectRatio', [1, 1, 1]);
+PhasePlot(oscillator_ode,
+ 'timepts', [0.25, 0.8, 2, 3], None, [
+ [-1, 1], [-0.3, 1], [0, 1], [0.25, 1], [0.5, 1], [0.75, 1], [1, 1],
+ [1, -1], [0.3, -1], [0, -1], [-0.25, -1], [-0.5, -1], [-0.75, -1], [-1, -1]
+ ], np.linspace(0, 8, 80))
+mpl.hold(True); mpl.plot([0], [0], 'k.'); # 'MarkerSize', AM_data_markersize*3);
+# set(gca,'DataAspectRatio',[1,1,1]);
+mpl.xlabel('x1'); mpl.ylabel('x2');
+
+mpl.show()
+
+#
+# Stability definitions
+#
+# This set of plots illustrates the various types of equilibrium points.
+#
+
+# Saddle point vector field
+def saddle_ode(x, t):
+ return (x[0] - 3*x[1], -3*x[0] + x[1]);
+
+# Asy stable
+m = 1; b = 1; k = 1; # default values
+mpl.figure(); mpl.clf();
+mpl.axis([-1, 1, -1, 1]); # set(gca, 'DataAspectRatio', [1 1 1]);
+PhasePlot(oscillator_ode, 'timepts', [0.3, 1, 2, 3], None,
+ [[-1,1], [-0.3,1], [0,1], [0.25,1], [0.5,1], [0.7,1], [1,1], [1.3,1],
+ [1,-1], [0.3,-1], [0,-1], [-0.25,-1], [-0.5,-1], [-0.7,-1], [-1,-1],
+ [-1.3,-1]],
+ np.linspace(0, 10, 100), parms = (m, b, k));
+mpl.hold(True); mpl.plot([0], [0], 'k.'); # 'MarkerSize', AM_data_markersize*3);
+# set(gca,'FontSize', 16);
+mpl.xlabel('{\itx}_1'); mpl.ylabel('{\itx}_2');
+
+# Saddle
+mpl.figure(); mpl.clf();
+mpl.axis([-1, 1, -1, 1]); # set(gca, 'DataAspectRatio', [1 1 1]);
+PhasePlot(saddle_ode, 'timepts', [0.2, 0.5, 0.8], None,
+ [ [-1, -1], [1, 1],
+ [-1, -0.95], [-1, -0.9], [-1, -0.8], [-1, -0.6], [-1, -0.4], [-1, -0.2],
+ [-0.95, -1], [-0.9, -1], [-0.8, -1], [-0.6, -1], [-0.4, -1], [-0.2, -1],
+ [1, 0.95], [1, 0.9], [1, 0.8], [1, 0.6], [1, 0.4], [1, 0.2],
+ [0.95, 1], [0.9, 1], [0.8, 1], [0.6, 1], [0.4, 1], [0.2, 1],
+ [-0.5, -0.45], [-0.45, -0.5], [0.5, 0.45], [0.45, 0.5],
+ [-0.04, 0.04], [0.04, -0.04] ], np.linspace(0, 2, 20));
+mpl.hold(True); mpl.plot([0], [0], 'k.'); # 'MarkerSize', AM_data_markersize*3);
+# set(gca,'FontSize', 16);
+mpl.xlabel('{\itx}_1'); mpl.ylabel('{\itx}_2');
+
+# Stable isL
+m = 1; b = 0; k = 1; # zero damping
+mpl.figure(); mpl.clf();
+mpl.axis([-1, 1, -1, 1]); # set(gca, 'DataAspectRatio', [1 1 1]);
+PhasePlot(oscillator_ode, 'timepts',
+ [pi/6, pi/3, pi/2, 2*pi/3, 5*pi/6, pi, 7*pi/6, 4*pi/3, 9*pi/6, 5*pi/3, 11*pi/6, 2*pi], None,
+ [ [0.2,0], [0.4,0], [0.6,0], [0.8,0], [1,0], [1.2,0], [1.4,0] ],
+ np.linspace(0, 20, 200), parms = (m, b, k));
+mpl.hold(True); mpl.plot([0], [0], 'k.') # 'MarkerSize', AM_data_markersize*3);
+# set(gca,'FontSize', 16);
+mpl.xlabel('{\itx}_1'); mpl.ylabel('{\itx}_2');
+
+mpl.show()
Added: trunk/src/phaseplot.py
===================================================================
--- trunk/src/phaseplot.py (rev 0)
+++ trunk/src/phaseplot.py 2011-07-25 05:00:41 UTC (rev 169)
@@ -0,0 +1,265 @@
+# phaseplot.py - generate 2D phase portraits
+#
+# Author: Richard M. Murray
+# Date: Fall 2002 (MATLAB version), based on a version by Kristi Morgansen
+#
+# Copyright (c) 2011 by California Institute of Technology
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# 3. The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import numpy as np
+import matplotlib.pyplot as mpl
+from matplotlib.mlab import frange, find
+from exception import ControlNotImplemented
+from scipy.integrate import odeint
+
+def PhasePlot(odefun, xlimv, ylimv, scale=1, xinit=None, T=None, parms=(),
+ verbose=True):
+ """
+ PhasePlot Phase plot for 2D dynamical systems
+
+ PhasePlot(F, X1range, X2range, scale) produces a quiver plot for
+ the function F. X1range and X2range have the form [min, max, num]
+ and specify the axis limits for the plot, along with the number of
+ subdivisions in each axis, used to produce an quiver plot for the
+ vector field. The vector field is scaled by a factor 'scale'
+ (default = 1).
+
+ The function F should be the same for as used for scipy.integrate.
+ Namely, it should be a function of the form dxdt = F(x, t) that
+ accepts a state x of dimension 2 and returns a derivative dxdt of
+ dimension 2.
+
+ PhasePlot(F, X1range, X2range, scale, Xinit) produces a phase
+ plot for the function F, consisting of the quiver plot plus stream
+ lines. The streamlines start at the initial conditions listed in
+ Xinit, which should be a matrix whose rows give the desired inital
+ conditions for x1 and x2. X1range or X2range is 'auto', the arrows
+ are produced based on the stream lines. If 'scale' is negative,
+ dots are placed at the base of the arrows. If 'scale' is zero, no
+ dots are produced.
+
+ PhasePlot(F, X1range, X2range, scale, boxgrid(X1range2, X2range2))
+ produces a phase plot with stream lines generated at the edges of
+ the rectangle defined by X1range2, X2range2. These ranges are in
+ the same form as X1range, X2range.
+
+ PhasePlot(F, X1range, X2range, scale, Xinit, T) produces a phase
+ plot where the streamlines are simluted for time T (default = 50).
+
+ PhasePlot(F, X1range, X2range, scale, Xinit, T, P1, P2, ...)
+ passes additional parameters to the function F, in the same way as
+ ODE45.
+
+ Instead of drawing arrows on a grid, arrows can also be drawn on
+ streamlines by usinge the X1range and X2range arguments as follows:
+
+ X1range X2range
+ ------- -------
+ 'auto' N Draw N arrows using equally space time points
+ 'logtime' [N, lam] Draw N arrows using exponential time constant lam
+ 'timepts' [t1, t2, ...] Draw arrows at the list times
+ """
+
+ #
+ # Parameters defining the plot
+ #
+ # The constants below define the parameters that control how the
+ # plot looks. These can be modified to customize the look of the
+ # phase plot.
+ #
+
+ #! TODO: convert to keywords
+ #! TODO: eliminate old parameters that aren't used
+ # PP color = ['m', 'c', 'r', 'g', 'b', 'k', 'y'];
+ PP_stream_color = ('b'); # color array for streamlines
+ PP_stream_linewidth = 1; # line width for stream lines
+
+ PP_arrow_linewidth = 1; # line width for arrows (quiver)
+ PP_arrow_markersize = 10; # size of arrow base marker
+
+ #
+ # Figure out ranges for phase plot (argument processing)
+ #
+ auto = 0; logtime = 0; timepts = 0; Narrows = 0;
+ if (isinstance(xlimv, str) and xlimv == 'auto'):
+ auto = 1;
+ Narrows = ylimv;
+ if (verbose):
+ print 'Using auto arrows\n';
+
+ elif (isinstance(xlimv, str) and xlimv == 'logtime'):
+ logtime = 1;
+ Narrows = ylimv[0];
+ timefactor = ylimv[1];
+ if (verbose):
+ print 'Using logtime arrows\n';
+
+ elif (isinstance(xlimv, str) and xlimv == 'timepts'):
+ timepts = 1;
+ Narrows = len(ylimv);
+
+ else:
+ # Figure out the set of points for the quiver plot
+ (x1, x2) = np.meshgrid(
+ frange(xlimv[0], xlimv[1], float(xlimv[1]-xlimv[0])/xlimv[2]),
+ frange(ylimv[0], ylimv[1], float(ylimv[1]-ylimv[0])/ylimv[2]));
+
+ if ((not auto) and (not logtime) and (not timepts)):
+ # Now calculate the vector field at those points
+ (nr,nc) = x1.shape;
+ dx = np.empty((nr, nc, 2))
+ for i in range(nr):
+ for j in range(nc):
+ dx[i, j, :] = np.squeeze(odefun((x1[i,j], x2[i,j]), 0, *parms))
+
+ # Plot the quiver plot
+ #! TODO: figure out arguments to make arrows show up correctly
+ if (scale == None):
+ mpl.quiver(x1, x2, dx[:,:,1], dx[:,:,2], angles='xy')
+ elif (scale != 0):
+ #! TODO: optimize parameters for arrows
+ #! TODO: figure out arguments to make arrows show up correctly
+ xy = mpl.quiver(x1, x2, dx[:,:,0]*np.abs(scale),
+ dx[:,:,1]*np.abs(scale), angles='xy')
+ # set(xy, 'LineWidth', PP_arrow_linewidth, 'Color', 'b');
+
+ #! TODO: Tweak the shape of the plot
+ # a=gca; set(a,'DataAspectRatio',[1,1,1]);
+ # set(a,'XLim',xlimv(1:2)); set(a,'YLim',ylimv(1:2));
+ mpl.xlabel('x1'); mpl.ylabel('x2');
+
+ # See if we should also generate the streamlines
+ if (xinit == None or len(xinit) == 0):
+ return
+
+ # Convert initial conditions to a numpy array
+ xinit = np.array(xinit);
+ (nr, nc) = np.shape(xinit);
+
+ # Generate some empty matrices to keep arrow information
+ x1 = np.empty((nr, Narrows)); x2 = np.empty((nr, Narrows));
+ dx = np.empty((nr, Narrows, 2))
+
+ # See if we were passed a simulation time
+ if (T == None):
+ T = 50
+
+ # Parse the time we were passed
+ TSPAN = T;
+ if (isinstance(T, (int, float))):
+ TSPAN = np.linspace(0, T, 100);
+
+ # Figure out the limits for the plot
+ if (scale == None):
+ # Assume that the current axis are set as we want them
+ alim = mpl.axis();
+ xmin = alim[0]; xmax = alim[1];
+ ymin = alim[2]; ymax = alim[3];
+ else:
+ # Use the maximum extent of all trajectories
+ xmin = np.min(xinit[:,0]); xmax = np.max(xinit[:,0]);
+ ymin = np.min(xinit[:,1]); ymax = np.max(xinit[:,1]);
+
+ # Generate the streamlines for each initial condition
+ for i in range(nr):
+ state = odeint(odefun, xinit[i], TSPAN, args=parms);
+ time = TSPAN
+ mpl.hold(True);
+ mpl.plot(state[:,0], state[:,1])
+ #! TODO: add back in colors for stream lines
+ # PP_stream_color(np.mod(i-1, len(PP_stream_color))+1));
+ # set(h[i], 'LineWidth', PP_stream_linewidth);
+
+ # Plot arrows if quiver parameters were 'auto'
+ if (auto or logtime or timepts):
+ # Compute the locations of the arrows
+ #! TODO: check this logic to make sure it works in python
+ for j in range(Narrows):
+
+ # Figure out starting index; headless arrows start at 0
+ k = -1 if scale == None else 0;
+
+ # Figure out what time index to use for the next point
+ if (auto):
+ # Use a linear scaling based on ODE time vector
+ tind = np.floor((len(time)/Narrows) * (j-k)) + k;
+ elif (logtime):
+ # Use an exponential time vector
+ # MATLAB: tind = find(time < (j-k) / lambda, 1, 'last');
+ tarr = find(time < (j-k) / timefactor);
+ tind = tarr[-1] if len(tarr) else 0;
+ elif (timepts):
+ # Use specified time points
+ # MATLAB: tind = find(time < ylimv[j], 1, 'last');
+ tarr = find(time < ylimv[j]);
+ tind = tarr[-1] if len(tarr) else 0;
+
+ # For tailless arrows, skip the first point
+ if (tind == 0 and scale == None):
+ continue;
+
+ # Figure out the arrow at this point on the curve
+ x1[i,j] = state[tind, 0];
+ x2[i,j] = state[tind, 1];
+
+ # Skip arrows outside of initial condition box
+ if (scale != None or
+ (x1[i,j] <= xmax and x1[i,j] >= xmin and
+ x2[i,j] <= ymax and x2[i,j] >= ymin)):
+ v = odefun((x1[i,j], x2[i,j]), 0, *parms)
+ dx[i, j, 0] = v[0]; dx[i, j, 1] = v[1];
+ else:
+ dx[i, j, 0] = 0; dx[i, j, 1] = 0;
+
+ # Set the plot shape before plotting arrows to avoid warping
+ # a=gca;
+ # if (scale != None):
+ # set(a,'DataAspectRatio', [1,1,1]);
+ # if (xmin != xmax and ymin != ymax):
+ # mpl.axis([xmin, xmax, ymin, ymax]);
+ # set(a, 'Box', 'on');
+
+ # Plot arrows on the streamlines
+ if (scale == None and Narrows > 0):
+ # Use a tailless arrow
+ #! TODO: figure out arguments to make arrows show up correctly
+ mpl.quiver(x1, x2, dx[:,:,0], dx[:,:,1], angles='xy')
+ elif (scale != 0 and Narrows > 0):
+ #! TODO: figure out arguments to make arrows show up correctly
+ xy = mpl.quiver(x1, x2, dx[:,:,0]*abs(scale), dx[:,:,1]*abs(scale),
+ angles='xy')
+ # set(xy, 'LineWidth', PP_arrow_linewidth);
+ # set(xy, 'AutoScale', 'off');
+ # set(xy, 'AutoScaleFactor', 0);
+
+ if (scale < 0):
+ bp = mpl.plot(x1, x2, 'b.'); # add dots at base
+ # set(bp, 'MarkerSize', PP_arrow_markersize);
+
+ return;
Added: trunk/tests/margin_test.py
===================================================================
--- trunk/tests/margin_test.py (rev 0)
+++ trunk/tests/margin_test.py 2011-07-25 05:00:41 UTC (rev 169)
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+#
+# margin_test.py - test suit for stability margin commands
+# RMM, 15 Jul 2011
+
+import unittest
+import numpy as np
+from control.xferfcn import TransferFunction
+from control.statesp import StateSpace
+from control.margin import *
+
+class TestMargin(unittest.TestCase):
+ """These are tests for the margin commands in margin.py."""
+
+ def setUp(self):
+ self.sys1 = TransferFunction([1, 2], [1, 2, 3])
+ self.sys2 = TransferFunction([1], [1, 2, 3, 4])
+ self.sys3 = StateSpace([[1., 4.], [3., 2.]], [[1.], [-4.]],
+ [[1., 0.]], [[0.]])
+
+ def testGainPhaseMargin(self):
+ gm, pm, sm, wg, wp, ws = StabilityMargins(self.sys1);
+ gm, pm, sm, wg, wp, ws = StabilityMargins(self.sys2);
+ gm, pm, sm, wg, wp, ws = StabilityMargins(self.sys3);
+
+ def testPhaseCrossoverFrequencies(self):
+ omega, gain = PhaseCrossoverFrequencies(self.sys2)
+ np.testing.assert_array_almost_equal(omega, [1.73205, 0.])
+ np.testing.assert_array_almost_equal(gain, [-0.5, 0.25])
+
+ tf = TransferFunction([1],[1,1])
+ omega, gain = PhaseCrossoverFrequencies(tf)
+ np.testing.assert_array_almost_equal(omega, [0.])
+ np.testing.assert_array_almost_equal(gain, [1.])
+
+ # testing MIMO, only (0,0) element is considered
+ tf = TransferFunction([[[1],[2]],[[3],[4]]],
+ [[[1, 2, 3, 4],[1,1]],[[1,1],[1,1]]])
+ omega, gain = PhaseCrossoverFrequencies(tf)
+ np.testing.assert_array_almost_equal(omega, [1.73205081, 0.])
+ np.testing.assert_array_almost_equal(gain, [-0.5, 0.25])
+
+def suite():
+ return unittest.TestLoader().loadTestsFromTestCase(TestMargin)
+
+if __name__ == "__main__":
+ unittest.main()
Added: trunk/tests/phaseplot_test.py
===================================================================
--- trunk/tests/phaseplot_test.py (rev 0)
+++ trunk/tests/phaseplot_test.py 2011-07-25 05:00:41 UTC (rev 169)
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+#
+# phaseplot_test.py - test phase plot functions
+# RMM, 17 24 2011 (based on TestMatlab from v0.4c)
+#
+# This test suite calls various phaseplot functions. Since the plots
+# themselves can't be verified, this is mainly here to make sure all
+# of the function arguments are handled correctly. If you run an
+# individual test by itself and then type show(), it should pop open
+# the figures so that you can check them visually.
+
+import unittest
+import numpy as np
+import scipy as sp
+import matplotlib.pyplot as mpl
+from control.phaseplot import *
+from numpy import pi
+
+class TestPhasePlot(unittest.TestCase):
+ def setUp(self):
+ pass;
+
+ def testInvPendNoSims(self):
+ PhasePlot(self.invpend_ode, (-6,6,10), (-6,6,10));
+
+ def testInvPendSims(self):
+ PhasePlot(self.invpend_ode, (-6,6,10), (-6,6,10),
+ xinit = ([1,1], [-1,1]));
+
+ def testInvPendTimePoints(self):
+ PhasePlot(self.invpend_ode, (-6,6,10), (-6,6,10),
+ xinit = ([1,1], [-1,1]), T=np.linspace(0,5,100));
+
+ def testInvPendLogtime(self):
+ PhasePlot(self.invpend_ode,
+ 'logtime', (3, 0.7), None,
+ [ [-2*pi, 1.6], [-2*pi, 0.5], [-1.8, 2.1],
+ [-1, 2.1], [4.2, 2.1], [5, 2.1],
+ [2*pi, -1.6], [2*pi, -0.5], [1.8, -2.1],
+ [1, -2.1], [-4.2, -2.1], [-5, -2.1] ],
+ np.linspace(0, 40, 200), verbose=False)
+
+ def testInvPendAuto(self):
+ PhasePlot(self.invpend_ode, 'auto', 0, None,
+ [[-2.3056, 2.1], [2.3056, -2.1]], 6, verbose=False)
+
+ def testOscillatorParams(self):
+ m = 1; b = 1; k = 1; # default values
+ PhasePlot(self.oscillator_ode, 'timepts', [0.3, 1, 2, 3], None,
+ [[-1,1], [-0.3,1], [0,1], [0.25,1], [0.5,1], [0.7,1],
+ [1,1], [1.3,1], [1,-1], [0.3,-1], [0,-1], [-0.25,-1],
+ [-0.5,-1], [-0.7,-1], [-1,-1], [-1.3,-1]],
+ np.linspace(0, 10, 100), parms = (m, b, k));
+
+ # Sample dynamical systems - inverted pendulum
+ def invpend_ode(self, x, t, m=1., l=1., b=0, g=9.8):
+ import numpy as np
+ return (x[1], -b/m*x[1] + (g*l/m) * np.sin(x[0]))
+
+ # Sample dynamical systems - oscillator
+ def oscillator_ode(self, x, t, m=1., b=1, k=1, extra=None):
+ return (x[1], -k/m*x[0] - b/m*x[1])
+
+def suite():
+ return unittest.TestLoader().loadTestsFromTestCase(TestPhasePlot)
+
+if __name__ == '__main__':
+ unittest.main()
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|