from sympy import Function, sympify, diff, Eq, S, Symbol, Derivative
from sympy.core.compatibility import combinations_with_replacement

def euler_equations(L, funcs=(), vars=()):
    """Find the Euler-Lagrange equations for a given Lagrangian.

    Parameters
    ==========
    L : Expr
        The Lagrangian that should be a function of the functions listed
        in the second argument and their first derivatives.

        For example, in the case of two functions (f(x,y), g(x,y)) and
        two independent variables (x,y) the Lagrangian would have the form::

            L(f(x,y),g(x,y),df/dx,df/dy,dg/dx,dg/dy,x,y)

    funcs : Function or list/tuple of Functions
        The functions that the Lagrangian depends on. The Euler equations
        are differential equations for each of these functions.

    vars : Symbol or list/tuple of Symbols
        The Symbols that are the independent variables of the functions.

    In many cases it is not necessary to provide anything, except the
    Lagrangian, it will be autodetected (and an error raised if this
    couldn't be done).

    Returns
    =======
    eqns : set of Eq
        The set of differential equations, one for each function.

    Examples
    ========

        >>> from sympy import Symbol, Function
        >>> from sympy.calculus.euler import euler_equations
        >>> x = Function('x')
        >>> t = Symbol('t')
        >>> L = (x(t).diff(t))**2/2 - x(t)**2/2
        >>> euler_equations(L, x(t), t)
        set([-x(t) - Derivative(x(t), t, t) == 0])
        >>> u = Function('u')
        >>> x = Symbol('x')
        >>> L = (u(t, x).diff(t))**2/2 - (u(t, x).diff(x))**2/2
        >>> euler_equations(L, u(t, x), [t, x])
        set([-Derivative(u(t, x), t, t) + Derivative(u(t, x), x, x) == 0])

    References
    ==========

    .. [1] http://en.wikipedia.org/wiki/Euler%E2%80%93Lagrange_equation

    """

    if not isinstance(funcs, (tuple, list)):
        funcs = (funcs,)

    if not funcs:
        funcs = tuple(L.atoms(Function))
    else:
        for f in funcs:
            if not isinstance(f, Function):
                raise TypeError('Function expected, got: %s' % f)

    if not isinstance(vars, (tuple, list)):
        vars = (vars,)

    if not vars:
        vars = funcs[0].args
    else:
        vars = tuple(sympify(var) for var in vars)

    if not all(isinstance(v, Symbol) for v in vars):
        raise TypeError('Variables are not symbols, got %s' % vars)

    for f in funcs:
        if not vars == f.args:
            raise ValueError("Variables %s don't match function arguments: %s" % (vars, f))

    order = max(len(d.variables) for d in L.atoms(Derivative) if d.expr in funcs)

    eqns = []
    for f in funcs:
        eq = diff(L, f)
        for i in range(1, order + 1):
            for p in combinations_with_replacement(vars, i):
                eq = eq + S.NegativeOne**i*diff(L, diff(f, *p), *p)
        eqns.append(Eq(eq, 0))

    return set(eqns)
