def child_structure_dfs(sampler, seen=None):
    """Return the structure of a composed sampler using a depth-first search on its
    children.

    Args:
        sampler (:obj:`.Sampler`):
            :class:`.Structured` or composed sampler with at least
            one structured child.

        seen (set, optional, default=False):
            IDs of already checked child samplers.

    Returns:
        :class:`~collections.namedtuple`: A named tuple of the form
        `Structure(nodelist, edgelist, adjacency)`, where the 3-tuple values
        are the :attr:`.Structured.nodelist`, :attr:`.Structured.edgelist`
        and :attr:`.Structured.adjacency` attributes of the first structured
        sampler found.

    Raises:
        ValueError: If no structured sampler is found.

    Examples:

    >>> sampler = dimod.TrackingComposite(
    ...                 dimod.StructureComposite(
    ...                 dimod.ExactSolver(), [0, 1], [(0, 1)]))
    >>> print(dimod.child_structure_dfs(sampler).nodelist)
    [0, 1]


    """
    seen = set() if seen is None else seen

    if sampler not in seen:
        try:
            return sampler.structure
        except AttributeError:
            # hasattr just tries to access anyway...
            pass

    seen.add(sampler)

    for child in getattr(sampler, 'children', ()):  # getattr handles samplers
        if child in seen:
            continue

        try:
            return child_structure_dfs(child, seen=seen)
        except ValueError:
            # tree has no child samplers
            pass

    raise ValueError("no structured sampler found")