def eval_graph(evaluator, variable): from sympy.plotting.plot import LineOver1DRangeSeries func = evaluator.eval("input_evaluated") series = LineOver1DRangeSeries(func, (variable, -10, 10), nb_of_points=200) series = series.get_points() return { 'function': sympy.jscode(sympy.sympify(func)), 'variable': repr(variable), 'xvalues': json.dumps(series[0].tolist()), 'yvalues': json.dumps(series[1].tolist()) }
def eval_plot(evaluator, components, parameters=None): if parameters is None: parameters = {} xmin, xmax = parameters.get('xmin', -10), parameters.get('xmax', 10) pmin, pmax = parameters.get('tmin', 0), parameters.get('tmax', 2 * sympy.pi) tmin, tmax = parameters.get('tmin', 0), parameters.get('tmax', 10) from sympy.plotting.plot import LineOver1DRangeSeries, Parametric2DLineSeries functions = evaluator.get("input_evaluated") if isinstance(functions, sympy.Basic): functions = [(functions, 'xy')] elif isinstance(functions, list): functions = [(f, 'xy') for f in functions] elif isinstance(functions, dict): functions = [(f, determine_graph_type(key)) for key, f in functions.items()] graphs = [] for func, graph_type in functions: if graph_type == 'parametric': x_func, y_func = func x_vars, y_vars = x_func.free_symbols, y_func.free_symbols variables = x_vars.union(y_vars) if x_vars != y_vars: raise ValueError( "Both functions in a parametric plot must have the same variable" ) else: variables = func.free_symbols if len(variables) > 1: raise ValueError("Cannot plot multivariate function") elif len(variables) == 0: variable = sympy.Symbol('x') else: variable = list(variables)[0] try: if graph_type == 'xy': graph_range = (variable, xmin, xmax) elif graph_type == 'polar': graph_range = (variable, pmin, pmax) elif graph_type == 'parametric': graph_range = (variable, tmin, tmax) if graph_type in ('xy', 'polar'): series = LineOver1DRangeSeries(func, graph_range, nb_of_points=150) elif graph_type == 'parametric': series = Parametric2DLineSeries(x_func, y_func, graph_range, nb_of_points=150) # returns a list of [[x,y], [next_x, next_y]] pairs series = series.get_segments() except TypeError: raise ValueError("Cannot plot function") xvalues = [] yvalues = [] def limit_y(y): CEILING = 1e8 if y > CEILING: y = CEILING if y < -CEILING: y = -CEILING return y x_transform, y_transform = GRAPH_TYPES[graph_type] series.append([series[-1][1], None]) for point in series: if point[0][1] is None: continue x = point[0][0] y = limit_y(point[0][1]) xvalues.append(x_transform(x, y)) yvalues.append(y_transform(x, y)) graphs.append({ 'type': graph_type, 'function': sympy.jscode(sympy.sympify(func)), 'points': { 'x': xvalues, 'y': yvalues }, 'data': None }) return {'variable': repr(variable), 'graphs': json.dumps(graphs)}
def bode_phase_numerical_data(system, initial_exp=-5, final_exp=5, **kwargs): """ Returns the numerical data of the Bode phase plot of the system. It is internally used by ``bode_phase_plot`` to get the data for plotting Bode phase plot. Users can use this data to further analyse the dynamics of the system or plot using a different backend/plotting-module. Parameters ========== system : SISOLinearTimeInvariant The system for which the Bode phase plot data is to be computed. initial_exp : Number, optional The initial exponent of 10 of the semilog plot. Defaults to -5. final_exp : Number, optional The final exponent of 10 of the semilog plot. Defaults to 5. Returns ======= tuple : (x, y) x = x-axis values of the Bode phase plot. y = y-axis values of the Bode phase plot. Raises ====== NotImplementedError When a SISO LTI system is not passed. When time delay terms are present in the system. ValueError When more than one free symbol is present in the system. The only variable in the transfer function should be the variable of the Laplace transform. Examples ======== >>> from sympy.abc import s >>> from sympy.physics.control.lti import TransferFunction >>> from sympy.physics.control.control_plots import bode_phase_numerical_data >>> tf1 = TransferFunction(s**2 + 1, s**4 + 4*s**3 + 6*s**2 + 5*s + 2, s) >>> bode_phase_numerical_data(tf1) # doctest: +SKIP ([1e-05, 1.4472354033813751e-05, 2.035581932165858e-05,..., 47577.3248186011, 67884.09326036123, 100000.0], [-2.5000000000291665e-05, -3.6180885085e-05, -5.08895483066e-05,...,-3.1415085799262523, -3.14155265358979]) See Also ======== bode_magnitude_plot, bode_phase_numerical_data """ _check_system(system) expr = system.to_expr() _w = Dummy("w", real=True) w_expr = expr.subs({system.var: I * _w}) phase = arg(w_expr) return LineOver1DRangeSeries(phase, (_w, 10**initial_exp, 10**final_exp), xscale='log', **kwargs).get_points()
def bode_magnitude_numerical_data(system, initial_exp=-5, final_exp=5, **kwargs): """ Returns the numerical data of the Bode magnitude plot of the system. It is internally used by ``bode_magnitude_plot`` to get the data for plotting Bode magnitude plot. Users can use this data to further analyse the dynamics of the system or plot using a different backend/plotting-module. Parameters ========== system : SISOLinearTimeInvariant The system for which the data is to be computed. initial_exp : Number, optional The initial exponent of 10 of the semilog plot. Defaults to -5. final_exp : Number, optional The final exponent of 10 of the semilog plot. Defaults to 5. Returns ======= tuple : (x, y) x = x-axis values of the Bode magnitude plot. y = y-axis values of the Bode magnitude plot. Raises ====== NotImplementedError When a SISO LTI system is not passed. When time delay terms are present in the system. ValueError When more than one free symbol is present in the system. The only variable in the transfer function should be the variable of the Laplace transform. Examples ======== >>> from sympy.abc import s >>> from sympy.physics.control.lti import TransferFunction >>> from sympy.physics.control.control_plots import bode_magnitude_numerical_data >>> tf1 = TransferFunction(s**2 + 1, s**4 + 4*s**3 + 6*s**2 + 5*s + 2, s) >>> bode_magnitude_numerical_data(tf1) # doctest: +SKIP ([1e-05, 1.5148378120533502e-05,..., 68437.36188804005, 100000.0], [-6.020599914256786, -6.0205999155219505,..., -193.4117304087953, -200.00000000260573]) See Also ======== bode_magnitude_plot, bode_phase_numerical_data """ _check_system(system) expr = system.to_expr() _w = Dummy("w", real=True) w_expr = expr.subs({system.var: I * _w}) mag = 20 * log(Abs(w_expr), 10) return LineOver1DRangeSeries(mag, (_w, 10**initial_exp, 10**final_exp), xscale='log', **kwargs).get_points()
def ramp_response_numerical_data(system, slope=1, prec=8, lower_limit=0, upper_limit=10, **kwargs): """ Returns the numerical values of the points in the ramp response plot of a SISO continuous-time system. By default, adaptive sampling is used. If the user wants to instead get an uniformly sampled response, then ``adaptive`` kwarg should be passed ``False`` and ``nb_of_points`` must be passed as additional kwargs. Refer to the parameters of class :class:`sympy.plotting.plot.LineOver1DRangeSeries` for more details. Parameters ========== system : SISOLinearTimeInvariant The system for which the ramp response data is to be computed. slope : Number, optional The slope of the input ramp function. Defaults to 1. prec : int, optional The decimal point precision for the point coordinate values. Defaults to 8. lower_limit : Number, optional The lower limit of the plot range. Defaults to 0. upper_limit : Number, optional The upper limit of the plot range. Defaults to 10. kwargs : Additional keyword arguments are passed to the underlying :class:`sympy.plotting.plot.LineOver1DRangeSeries` class. Returns ======= tuple : (x, y) x = Time-axis values of the points in the ramp response plot. NumPy array. y = Amplitude-axis values of the points in the ramp response plot. NumPy array. Raises ====== NotImplementedError When a SISO LTI system is not passed. When time delay terms are present in the system. ValueError When more than one free symbol is present in the system. The only variable in the transfer function should be the variable of the Laplace transform. When ``lower_limit`` parameter is less than 0. When ``slope`` is negative. Examples ======== >>> from sympy.abc import s >>> from sympy.physics.control.lti import TransferFunction >>> from sympy.physics.control.control_plots import ramp_response_numerical_data >>> tf1 = TransferFunction(s, s**2 + 5*s + 8, s) >>> ramp_response_numerical_data(tf1) # doctest: +SKIP (([0.0, 0.12166980856813935,..., 9.861246379582118, 10.0], [1.4504508011325967e-09, 0.006046440489058766,..., 0.12499999999568202, 0.12499999999661349])) See Also ======== ramp_response_plot """ if slope < 0: raise ValueError("Slope must be greater than or equal" " to zero.") if lower_limit < 0: raise ValueError("Lower limit of time must be greater " "than or equal to zero.") _check_system(system) _x = Dummy("x") expr = (slope * system.to_expr()) / ((system.var)**2) expr = apart(expr, system.var, full=True) _y = _fast_inverse_laplace(expr, system.var, _x).evalf(prec) return LineOver1DRangeSeries(_y, (_x, lower_limit, upper_limit), **kwargs).get_points()
def impulse_response_numerical_data(system, prec=8, lower_limit=0, upper_limit=10, **kwargs): """ Returns the numerical values of the points in the impulse response plot of a SISO continuous-time system. By default, adaptive sampling is used. If the user wants to instead get an uniformly sampled response, then ``adaptive`` kwarg should be passed ``False`` and ``nb_of_points`` must be passed as additional kwargs. Refer to the parameters of class :class:`sympy.plotting.plot.LineOver1DRangeSeries` for more details. Parameters ========== system : SISOLinearTimeInvariant The system for which the impulse response data is to be computed. prec : int, optional The decimal point precision for the point coordinate values. Defaults to 8. lower_limit : Number, optional The lower limit of the plot range. Defaults to 0. upper_limit : Number, optional The upper limit of the plot range. Defaults to 10. kwargs : Additional keyword arguments are passed to the underlying :class:`sympy.plotting.plot.LineOver1DRangeSeries` class. Returns ======= tuple : (x, y) x = Time-axis values of the points in the impulse response. NumPy array. y = Amplitude-axis values of the points in the impulse response. NumPy array. Raises ====== NotImplementedError When a SISO LTI system is not passed. When time delay terms are present in the system. ValueError When more than one free symbol is present in the system. The only variable in the transfer function should be the variable of the Laplace transform. When ``lower_limit`` parameter is less than 0. Examples ======== >>> from sympy.abc import s >>> from sympy.physics.control.lti import TransferFunction >>> from sympy.physics.control.control_plots import impulse_response_numerical_data >>> tf1 = TransferFunction(s, s**2 + 5*s + 8, s) >>> impulse_response_numerical_data(tf1) # doctest: +SKIP ([0.0, 0.06616480200395854,... , 9.854500743565858, 10.0], [0.9999999799999999, 0.7042848373025861,...,7.170748906965121e-13, -5.1901263495547205e-12]) See Also ======== impulse_response_plot """ if lower_limit < 0: raise ValueError("Lower limit of time must be greater " "than or equal to zero.") _check_system(system) _x = Dummy("x") expr = system.to_expr() expr = apart(expr, system.var, full=True) _y = _fast_inverse_laplace(expr, system.var, _x).evalf(prec) return LineOver1DRangeSeries(_y, (_x, lower_limit, upper_limit), **kwargs).get_points()
def step_response_numerical_data(system, prec=8, lower_limit=0, upper_limit=10, **kwargs): """ Returns the numerical values of the points in the step response plot of a SISO continuous-time system. By default, adaptive sampling is used. If the user wants to instead get an uniformly sampled response, then ``adaptive`` kwarg should be passed ``False`` and ``nb_of_points`` must be passed as additional kwargs. Refer to the parameters of class :class:`sympy.plotting.plot.LineOver1DRangeSeries` for more details. Parameters ========== system : SISOLinearTimeInvariant The system for which the unit step response data is to be computed. prec : int, optional The decimal point precision for the point coordinate values. Defaults to 8. lower_limit : Number, optional The lower limit of the plot range. Defaults to 0. upper_limit : Number, optional The upper limit of the plot range. Defaults to 10. kwargs : Additional keyword arguments are passed to the underlying :class:`sympy.plotting.plot.LineOver1DRangeSeries` class. Returns ======= tuple : (x, y) x = Time-axis values of the points in the step response. NumPy array. y = Amplitude-axis values of the points in the step response. NumPy array. Raises ====== NotImplementedError When a SISO LTI system is not passed. When time delay terms are present in the system. ValueError When more than one free symbol is present in the system. The only variable in the transfer function should be the variable of the Laplace transform. When ``lower_limit`` parameter is less than 0. Examples ======== >>> from sympy.abc import s >>> from sympy.physics.control.lti import TransferFunction >>> from sympy.physics.control.control_plots import step_response_numerical_data >>> tf1 = TransferFunction(s, s**2 + 5*s + 8, s) >>> step_response_numerical_data(tf1) # doctest: +SKIP ([0.0, 0.025413462339411542, 0.0484508722725343, ... , 9.670250533855183, 9.844291913708725, 10.0], [0.0, 0.023844582399907256, 0.042894276802320226, ..., 6.828770759094287e-12, 6.456457160755703e-12]) See Also ======== step_response_plot """ if lower_limit < 0: raise ValueError("Lower limit of time must be greater " "than or equal to zero.") _check_system(system) _x = Dummy("x") expr = system.to_expr() / (system.var) expr = apart(expr, system.var, full=True) _y = _fast_inverse_laplace(expr, system.var, _x).evalf(prec) return LineOver1DRangeSeries(_y, (_x, lower_limit, upper_limit), **kwargs).get_points()
def bode_phase_numerical_data(system, initial_exp=-5, final_exp=5, freq_unit='rad/sec', phase_unit='rad', **kwargs): """ Returns the numerical data of the Bode phase plot of the system. It is internally used by ``bode_phase_plot`` to get the data for plotting Bode phase plot. Users can use this data to further analyse the dynamics of the system or plot using a different backend/plotting-module. Parameters ========== system : SISOLinearTimeInvariant The system for which the Bode phase plot data is to be computed. initial_exp : Number, optional The initial exponent of 10 of the semilog plot. Defaults to -5. final_exp : Number, optional The final exponent of 10 of the semilog plot. Defaults to 5. freq_unit : string, optional User can choose between ``'rad/sec'`` (radians/second) and '``'Hz'`` (Hertz) as frequency units. phase_unit : string, optional User can choose between ``'rad'`` (radians) and ``'deg'`` (degree) as phase units. Returns ======= tuple : (x, y) x = x-axis values of the Bode phase plot. y = y-axis values of the Bode phase plot. Raises ====== NotImplementedError When a SISO LTI system is not passed. When time delay terms are present in the system. ValueError When more than one free symbol is present in the system. The only variable in the transfer function should be the variable of the Laplace transform. When incorrect frequency or phase units are given as input. Examples ======== >>> from sympy.abc import s >>> from sympy.physics.control.lti import TransferFunction >>> from sympy.physics.control.control_plots import bode_phase_numerical_data >>> tf1 = TransferFunction(s**2 + 1, s**4 + 4*s**3 + 6*s**2 + 5*s + 2, s) >>> bode_phase_numerical_data(tf1) # doctest: +SKIP ([1e-05, 1.4472354033813751e-05, 2.035581932165858e-05,..., 47577.3248186011, 67884.09326036123, 100000.0], [-2.5000000000291665e-05, -3.6180885085e-05, -5.08895483066e-05,...,-3.1415085799262523, -3.14155265358979]) See Also ======== bode_magnitude_plot, bode_phase_numerical_data """ _check_system(system) expr = system.to_expr() freq_units = ('rad/sec', 'Hz') phase_units = ('rad', 'deg') if freq_unit not in freq_units: raise ValueError( 'Only "rad/sec" and "Hz" are accepted frequency units.') if phase_unit not in phase_units: raise ValueError('Only "rad" and "deg" are accepted phase units.') _w = Dummy("w", real=True) if freq_unit == 'Hz': repl = I * _w * 2 * pi else: repl = I * _w w_expr = expr.subs({system.var: repl}) if phase_unit == 'deg': phase = arg(w_expr) * 180 / pi else: phase = arg(w_expr) x, y = LineOver1DRangeSeries(phase, (_w, 10**initial_exp, 10**final_exp), xscale='log', **kwargs).get_points() return x, y
def __init__(self, plot, detail): super().__init__(plot, detail) check = check_arguments([self._plot.get_body()], 1, 1)[0] self._series = LineOver1DRangeSeries(*check)