def suggest_splits(
        amount_msat: int,
        channels_with_funds: ChannelsFundsInfo,
        exclude_single_part_payments=False,
        exclude_multinode_payments=False,
        exclude_single_channel_splits=False
) -> List[SplitConfigRating]:
    """Breaks amount_msat into smaller pieces and distributes them over the
    channels according to the funds they can send.

    Individual channels may be assigned multiple parts. The split configurations
    are returned in sorted order, from best to worst rating.

    Single part payments can be excluded, since they represent legacy payments.
    Split configurations that send via multiple nodes can be excluded as well.
    """

    configs = []
    channel_keys = list(channels_with_funds.keys())

    # generate multiple configurations to get more configurations (there is randomness in this loop)
    for _ in range(CANDIDATES_PER_LEVEL):
        # we want to have configurations with no splitting to many splittings
        for target_parts in range(1, MAX_PARTS):
            config = SplitConfig()
            # randomly split amount into target_parts chunks
            split_amounts = split_amount_normal(amount_msat, target_parts)
            # randomly distribute amounts over channels
            for amount in split_amounts:
                random.shuffle(channel_keys)
                # we check each channel and try to put the funds inside, break if we succeed
                for c in channel_keys:
                    if c not in config:
                        config[c] = []
                    channel_funds, channel_slots = channels_with_funds[c]
                    if sum(config[c]) + amount <= channel_funds and len(config[c]) < channel_slots:
                        config[c].append(amount)
                        break
                # if we don't succeed to put the amount anywhere,
                # we try to fill up channels and put the rest somewhere else
                else:
                    distribute_amount = amount
                    for c in channel_keys:
                        channel_funds, channel_slots = channels_with_funds[c]
                        slots_left = channel_slots - len(config[c])
                        if slots_left == 0:
                            # no slot left in that channel
                            continue
                        funds_left = channel_funds - sum(config[c])
                        # it would be good to not fill the full channel if possible
                        add_amount = min(funds_left, distribute_amount)
                        config[c].append(add_amount)
                        distribute_amount -= add_amount
                        if distribute_amount == 0:
                            break
            if config.total_config_amount() != amount_msat:
                continue
            if target_parts > 1 and config.is_any_amount_smaller_than_min_part_size():
                if target_parts == 2:
                    # if there are already too small parts at the first split excluding single
                    # part payments may return only few configurations, this will allow single part
                    # payments for more payments, if they are too small to split
                    exclude_single_part_payments = False
                continue
            assert config.total_config_amount() == amount_msat
            configs.append(config)
        if not configs:
            raise NoPathFound('Cannot distribute payment over channels.')

    configs = remove_duplicates(configs)

    # we only take configurations that send via a single node (but there can be multiple parts)
    if exclude_multinode_payments:
        configs = remove_multiple_nodes(configs)

    if exclude_single_part_payments:
        configs = remove_single_part_configs(configs)

    if exclude_single_channel_splits:
        configs = remove_single_channel_splits(configs)

    rated_configs = [SplitConfigRating(
        config=c,
        rating=rate_config(c, channels_with_funds)
    ) for c in configs]
    rated_configs.sort(key=lambda x: x.rating)

    return rated_configs
Breaks amount_msat into smaller pieces and distributes them over the
    channels according to the funds they can send.

    Individual channels may be assigned multiple parts. The split configurations
    are returned in sorted order, from best to worst rating.

    Single part payments can be excluded, since they represent legacy payments.
    Split configurations that send via multiple nodes can be excluded as well.
    
electrum/mpp_split.py