Source code for pydy.utils

#!/usr/bin/env python

import re
import textwrap
import itertools

from packaging.version import parse as parse_version
import sympy as sm
import sympy.physics.mechanics as me
import numpy as np

SYMPY_VERSION = sm.__version__


def _sort_velocity_constraints(velocity_constraints, q, qdot_exprs):
    """Return the time differentiated holonomic constraints and nonholonomic
    constraints separately.

    Given velcoity leval constraings that may be a combination of time
    differentiated holonomic constraints and nonholonomic constraints, this
    function uses the symmetry of second derivatives check to distinguish the
    constraints.

    If the velocity constraints are large expressions, this could be slow due
    to the need to simplify for zero checking.

    Parameters
    ==========
    velocity_constraints : iterable of Expr
    q : iterable of Function of time
        Generalized coordinates.
    qdot_exprs : iteraable of Expr
        Expressions for the q' definitions.

    Returns
    =======
    holonomic_idxs : list of integer
        Indices that identify the rows of ``velocity_constraints`` which are
        time differentiated holonomic constraints.
    nonholonomic_idxs : list of integer
        Indices that identify the rows of ``velocity_constraints`` which are
        nonholonomic constraints.

    """
    # TODO : This could be something useful to implement in SymPy.
    velocity_constraints = sm.Matrix(velocity_constraints)
    jac, _ = sm.linear_eq_to_matrix(velocity_constraints, qdot_exprs)
    nonholonomic_idxs = []
    idxs = list(range(len(qdot_exprs)))
    comb_idxs = itertools.combinations(idxs, 2)
    for i, row in enumerate(jac.tolist()):
        for (j, k) in comb_idxs:
            zero = row[j].diff(q[k]) - row[k].diff(q[j])
            syms = list(zero.atoms(sm.Symbol) | me.find_dynamicsymbols(zero))
            eval_zero = sm.lambdify(syms, zero)
            vals = np.random.random(len(syms))
            if not np.allclose(eval_zero(*vals), 0.0, atol=1e-12):
                nonholonomic_idxs.append(i)
                break
    all_idxs = range(len(velocity_constraints))
    holonomic_idxs = [i for i in all_idxs if i not in nonholonomic_idxs]

    return holonomic_idxs, nonholonomic_idxs


[docs] def sympy_equal_to_or_newer_than(version, installed_version=None): """Returns true if the installed version of SymPy is equal to or newer than the provided version string.""" if installed_version is None: v = SYMPY_VERSION else: v = installed_version if v.endswith('-git'): msg = ('You are using an older development version of SymPy with a ' 'non-PEP440 compliant version number: {}. Please install ' 'a newer development version of SymPy.') raise ValueError(msg.format(v)) return parse_version(v) >= parse_version(version)
[docs] def sympy_newer_than(version): """Returns true if the installed version of SymPy is newer than the provided version string.""" return parse_version(SYMPY_VERSION) > parse_version(version)
[docs] def wrap_and_indent(lines, indentation=4, width=79, continuation=None, comment=None): """Returns a single string in which the lines have been indented and wrapped into a block of text. Parameters ========== indentation : integer The number of characters to indent. width : integer The maximum line width. continuation : string The continuation characters. comment : string The character that designates a comment line. """ if continuation is None: cont_len = 0 else: cont_len = len(continuation) if comment is None: comment_len = 0 else: comment_len = len(comment) # TODO : This will indent any lines that only contain a new line. Which # may not be preferable. new_lines = [] # TODO : The Octave printer has ".*" and "./" as operators and this doesn't # deal with that. # add whitespace before and after [*/] binary operands between # subexpressions and input/output pattern = re.compile(r'(\w\])([*/])(\w)') for line in lines: if line != '\n': line = pattern.sub(lambda m: ' '.join(m.groups()), line) wrapped = textwrap.wrap(line, width=width-indentation-cont_len-comment_len, break_long_words=False) if continuation: last = wrapped[-1] wrapped = [l + continuation for l in wrapped[:-1]] wrapped.append(last) if comment: for i, l in enumerate(wrapped[1:]): wrapped[i + 1] = comment + ' ' + l else: wrapped = [line] new_lines += wrapped spacer = '\n' + ' ' * indentation return ' ' * indentation + spacer.join(new_lines)
[docs] class PyDyDeprecationWarning(DeprecationWarning): pass
[docs] class PyDyImportWarning(ImportWarning): pass
[docs] class PyDyFutureWarning(FutureWarning): pass
[docs] class PyDyUserWarning(UserWarning): pass