~jb753/clusterfunc

b79d5e5ddcc30c5aa3b5e9d3b5014335bd820130 — James Brind 11 months ago 25e9483
stuff
A clusterfunc/plot.py => clusterfunc/plot.py +35 -0
@@ 0,0 1,35 @@
import matplotlib.pyplot as plt
import numpy as np

def plot(x):
    n = np.linspace(0, len(x)-1, len(x))
    fig, ax = plt.subplots()
    ax.plot(n, x, 'k-x')
    ax.plot(np.zeros_like(x), x, 'k-x')
    ax.set_xlim(n[0]-1, n[-1])
    ax.set_ylim(x.min(), x.max())
    ax.set_xlabel('Index')
    ax.set_ylabel('Coordinate')

def plot_ER(x):
    ER = clusterfunc.util.ER(x)
    n = np.linspace(0, len(ER)-1, len(ER))
    fig, ax = plt.subplots()
    ax.plot(n, ER, 'k-x')
    # ax.plot(np.zeros_like(x), x, 'k-x')
    ax.set_xlim(n[0]-1, n[-1])
    # ax.set_ylim(x.min(), x.max())
    ax.set_xlabel('Index')
    ax.set_ylabel('Coordinate')

import clusterfunc.single
import clusterfunc.symmetric

Dmin = 1e-1
Dmax = 2e-1
ERmax = 1.2
# x = clusterfunc.single.unit_free_N(Dmin, Dmax, ERmax, mult=1)
x = clusterfunc.symmetric.unit_free_N(Dmin, Dmax, ERmax, 1)
plot(x)
# plot_ER(x)
plt.show()

M clusterfunc/single.py => clusterfunc/single.py +11 -5
@@ 3,8 3,9 @@ import numpy as np
import clusterfunc.util
from clusterfunc.exceptions import ClusteringException
from scipy.optimize import root_scalar
import clusterfunc.check

def unit_fixed_N(Dmin, Dmax, ERmax, N):
def unit_fixed_N(Dmin, Dmax, ERmax, N, check=True):
    """Single-sided clustering on the unit interval with specified number of points.

    Parameters


@@ 132,6 133,10 @@ def unit_fixed_N(Dmin, Dmax, ERmax, N):
    if not len(x) == N:
        raise ClusteringException(f'Incorrect number of points len(x)={len(x)}, expected {N}.')

    if check:
        clusterfunc.check.unit_single(x, Dmin, Dmax, ERmax)


    return x




@@ 140,7 145,7 @@ def unit_free_N(Dmin, Dmax, ERmax, mult=8):

    # Find high and low guesses for n, N = mult*n + 1
    nlow = np.round(1./Dmax/mult).astype(int)
    nhigh = np.minimum(np.ceil(1./Dmin/mult).astype(int),1024) # Limit to protect against overflow
    nhigh = np.minimum(np.floor(1./Dmin/mult).astype(int),1024) # Limit to protect against overflow
    if nlow > nhigh:
        nlow = 1
    if nhigh == nlow:


@@ 148,8 153,9 @@ def unit_free_N(Dmin, Dmax, ERmax, mult=8):

    Nhigh = nhigh * mult + 1
    try:
        x = unit_fixed_N(Dmin, Dmax, ERmax, Nhigh)
    except ClusteringException:
        x = unit_fixed_N(Dmin, Dmax, ERmax, Nhigh, check=True)
    except ClusteringException as e:
        print(e)
        raise ClusteringException(f'Failed to cluster with high number of points guess Nhigh={Nhigh}')

    # Loop until converged


@@ 161,7 167,7 @@ def unit_free_N(Dmin, Dmax, ERmax, mult=8):

        # Attempt to cluster
        try:
            x = unit_fixed_N(Dmin, Dmax, ERmax, Nnew)
            x = unit_fixed_N(Dmin, Dmax, ERmax, Nnew, check=True)
            # New n is valid, use as high limiit
            nhigh = nnew
        except ClusteringException:

M clusterfunc/symmetric.py => clusterfunc/symmetric.py +26 -5
@@ 4,9 4,10 @@ from scipy.optimize import root_scalar
from clusterfunc.exceptions import ClusteringException
import clusterfunc.check
import clusterfunc.util
import clusterfunc.single


def symmetric_fixed_N(Dmin, Dmax, ERmax, N):
def unit_fixed_N(Dmin, Dmax, ERmax, N):
    """"""
    if N < 3:
        raise ClusteringException(f'Not enough points N={N} to symmetrically cluster, need N>=3')


@@ 23,8 24,8 @@ def symmetric_fixed_N(Dmin, Dmax, ERmax, N):
        Lb = (1.-xj)

        # Evaluate the halves
        xa = single_fixed_N(Dmin/La, Dmax/La, ERmax, Na)*La
        xb = 1.-np.flip(single_fixed_N(Dmin/Lb, Dmax/Lb, ERmax, Nb))*Lb
        xa = clusterfunc.single.fixed_N(0., La, Dmin, Dmax, ERmax, Na)
        xb = 1.-np.flip(clusterfunc.single.fixed_N(0., Lb, Dmin, Dmax, ERmax, Nb))
        x = np.concatenate((xa, xb[1:]))

        dxa = np.diff(xa)[-1]


@@ 60,11 61,31 @@ def symmetric_fixed_N(Dmin, Dmax, ERmax, N):

    return x

def symmetric_free_N(Dmin, Dmax, ERmax, mult=8):
def unit_free_N(Dmin, Dmax, ERmax, mult=8):
    """"""
    xa = single_free_N(Dmin*2., Dmax*2., ERmax, mult)/2.
    xa = clusterfunc.single.free_N(0., 0.5, Dmin, Dmax, ERmax, mult)
    assert np.diff(xa).max() * (1.+1e-9) <= Dmax
    xb = 1.-np.flip(xa)
    x = np.concatenate((xa, xb[1:]))
    assert not np.mod((len(x)-1)/mult,1.)
    return x

def fixed_N(x0, x1, Dmin, Dmax, ERmax, N):
    """Double-sided symmetric clustering between two values, fixed number of points."""
    if np.isclose(x0,x1):
        raise ClusteringException(f'Cannot distribute points without distinct start and end points, got x0={x0} and x1={x1}')
    dx = x1-x0
    dxa = np.abs(dx)
    x = x0 + dx * unit_fixed_N(Dmin/dxa, Dmax/dxa, ERmax, N)
    return x


def free_N(x0, x1, Dmin, Dmax, ERmax, mult=8):
    """Double-sided symmetrirc clustering between two values, free number of points."""
    if np.isclose(x0,x1):
        raise ClusteringException(f'Cannot distribute points without distinct start and end points, got x0={x0} and x1={x1}')
    dx = x1-x0
    dxa = np.abs(dx)
    x = x0 + dx * unit_free_N(Dmin/dxa, Dmax/dxa, ERmax, mult)
    return x


M clusterfunc/util.py => clusterfunc/util.py +6 -0
@@ 9,3 9,9 @@ def split_cells(N):
def cumsum0(x, axis=None):
    return np.insert(np.cumsum(x, axis=axis), 0, 0.0, axis=axis)

def ER(x):
    dx = np.diff(x)
    ER = dx[1:]/dx[:-1]
    ER[ER<1.] = 1./ER[ER<1.]
    return ER


A matplotlibrc => matplotlibrc +1 -0
@@ 0,0 1,1 @@
text.usetex : False

M tests/not_test_unit.py => tests/not_test_unit.py +0 -25
@@ 40,28 40,3 @@ def test_unif():

        # Check
        clusterfunc.check.unit_single(x, Dmin, Dmax, ER, rtol=1e-9)

def test_symmetric():

    for Dmin in  (1e-2, 1e-3, 1e-4, 1e-5):
        for ER in (1.02, 1.05, 1.1, 1.2):

            # Number of points for uniform, limit to not be too huge
            Nmax = np.minimum(np.floor(1./Dmin).astype(int), 256)

            for Dmax in (0.05, 0.1, 0.2, 1.):

                if Dmax <= Dmin:
                    continue

                # Minimum number of points with capping
                Nmin = len(clusterfunc.unit.symmetric_free_N(Dmin, Dmax, ER, mult=1))

                # Test without capping
                for N in range(Nmin,Nmax+1):

                    # Evaluate
                    x = clusterfunc.unit.symmetric_fixed_N(Dmin, Dmax, ER, N)

                    # Check
                    clusterfunc.check.unit_symmetric(x, Dmin, Dmax, ER, rtol=1e-9)

M tests/test_single.py => tests/test_single.py +2 -2
@@ 64,5 64,5 @@ def test_abs():
                        assert np.allclose(x[(0,-1),], (x0, x1))
                        dx = np.diff(x)
                        assert np.diff(np.sign(dx)).all() == 0.
                        assert np.abs(dx.max()) <= Dmax * (1.+rtol)
                        assert np.abs(dx.min()) * (1.+rtol) >= Dmin
                        assert np.abs(dx).max() <= Dmax * (1.+rtol)
                        assert np.abs(dx).min() * (1.+rtol) >= Dmin

A tests/test_symmetric.py => tests/test_symmetric.py +63 -0
@@ 0,0 1,63 @@
import clusterfunc.symmetric
import clusterfunc.check
import numpy as np
import pytest
from clusterfunc.exceptions import ClusteringException


def test_symmetric_unit():

    for Dmin in  (1e-1, 1e-2, 1e-3, 1e-4, 1e-5):
        for ER in (1.02, 1.05, 1.1, 1.2):

            # Number of points for uniform, limit to not be too huge
            Nmax = np.minimum(np.floor(1./Dmin).astype(int), 256)

            for Dmax in (0.05, 0.1, 0.2, 1.):

                if Dmax <= Dmin:
                    continue

                # Minimum number of points with capping
                Nmin = len(clusterfunc.symmetric.unit_free_N(Dmin, Dmax, ER, mult=1))
                print(Nmin)

                # Test without capping
                for N in range(Nmin,Nmax+1):
                    print(N)

                    # Evaluate
                    x = clusterfunc.symmetric.unit_fixed_N(Dmin, Dmax, ER, N)

                    # Check
                    clusterfunc.check.unit_symmetric(x, Dmin, Dmax, ER, rtol=1e-9)

                    dx = np.diff(x)
                    rtol = 1e-9
                    assert np.all(dx<= Dmax * (1.+rtol))
                    assert np.all(dx* (1.+rtol)>= Dmin )
                    assert np.diff(np.sign(dx)).all() == 0.

def test_symmetric_abs():

    rtol = 1e-9
    ER = 1.2
    for x0 in (-1., 0., 1., 2.):
        for x1 in (-1., 0., 1., 2.):
            for Dmin in  (1e-1, 1e-2, 1e-4):
                for Dmax in (0.05, 0.1, 0.2, 1.):

                    if Dmax <= Dmin:
                        continue

                    if np.isclose(x0,x1):
                        with pytest.raises(ClusteringException):
                            clusterfunc.symmetric.free_N(x0, x1, Dmin, Dmax, ER, mult=1)
                    else:
                        x = clusterfunc.symmetric.free_N(x0, x1, Dmin, Dmax, ER, mult=1)

                        assert np.allclose(x[(0,-1),], (x0, x1))
                        dx = np.diff(x)
                        assert np.diff(np.sign(dx)).all() == 0.
                        assert np.abs(dx).max() <= Dmax * (1.+rtol)
                        assert np.abs(dx).min() * (1.+rtol) >= Dmin