import numpy as np

def hopf_charge(n):
    if n.ndim!=5 or n.shape[-2]!=1: return np.nan

    def inner(a,b):
        return np.sum(a*b, axis=-1)

    def cross(a,b):
        return np.cross(a,b)

    def cumtrapezoid(a, axis):
        c = np.cumsum(a, axis=axis)/2
        r = c.copy()
        i1 = [slice(None)]*a.ndim
        i2 = [slice(None)]*a.ndim
        i1[axis] = slice(1,None)
        i2[axis] = slice(None,-1)
        r[tuple(i1)] += c[tuple(i2)]
        return r

    def normalize(n):
        return n/np.sqrt(np.sum(n**2, axis=-1))[...,None]

    # Indices after _ list axes in which direction lattice is shifted by half lattice constant.

    n_xy = normalize(n[1:,1:,:]+n[:-1,1:,:]+n[1:,:-1,:]+n[:-1,:-1,:]) #/4
    n_xz = normalize(n[1:,:,1:]+n[:-1,:,1:]+n[1:,:,:-1]+n[:-1,:,:-1])
    n_yz = normalize(n[:,1:,1:]+n[:,:-1,1:]+n[:,1:,:-1]+n[:,:-1,:-1])

    dx = n[1:,:,:] - n[:-1,:,:]
    dy = n[:,1:,:] - n[:,:-1,:]
    dz = n[:,:,1:] - n[:,:,:-1]

    dy_x = (dy[1:,:,:]+dy[:-1,:,:])/2
    dz_x = (dz[1:,:,:]+dz[:-1,:,:])/2
    dx_y = (dx[:,1:,:]+dx[:,:-1,:])/2
    dz_y = (dz[:,1:,:]+dz[:,:-1,:])/2
    dx_z = (dx[:,:,1:]+dx[:,:,:-1])/2
    dy_z = (dy[:,:,1:]+dy[:,:,:-1])/2


    fx_yz = 2*inner(n_yz,cross(dy_z, dz_y))
    # fy = 2*inner(n_xz,cross(dz_x, dx_z))
    fz_xy = 2*inner(n_xy,cross(dx_y, dy_x))

    fx_xyz = (fx_yz[1:,:,:]+fx_yz[:-1,:,:])/2
    fz_xyz = (fz_xy[:,:,1:]+fz_xy[:,:,:-1])/2

    # Ax = - int_-infty^y fz dy
    # Ay =   0
    # Az =   int_-infty^y fx dy

    ax_xy = - cumtrapezoid(fz_xy, axis=1)
    # ay =   0
    az_yz =   cumtrapezoid(fx_yz, axis=1)

    ax_xyz = (ax_xy[:,:,1:]+ax_xy[:,:,:-1])/2
    az_xyz = (az_yz[1:,:,:]+az_yz[:-1,:,:])/2

    fa = fx_xyz*ax_xyz+fz_xyz*az_xyz
    return -np.sum(fa)/(8*np.pi)**2
