def _update_boundary(self, x, y):

        N = self.waveguide_params.get("N")
        eta = self.waveguide_params.get("eta")
        print "N:", N
        print "eta: ", eta

        k0, k1 = [ np.sqrt(N**2 - n**2)*np.pi for n in 0, 1 ]
        L = abs(2*np.pi/(k0 - k1 + y))

        if neumann:
            WG = Neumann(L=L, loop_type='Constant', x_R0=x, y_R0=y,
                         **self.waveguide_params)
        else:
            WG = Dirichlet(L=L, loop_type='Constant', x_R0=x, y_R0=y,
                           **self.waveguide_params)
        self.WG = WG

        xi_lower, xi_upper = WG.get_boundary(eps=x, delta=y)

        np.savetxt("lower.profile", zip(WG.t, xi_lower))
        np.savetxt("upper.profile", zip(WG.t, xi_upper))

        N_file = len(WG.t)
        replacements = {'LENGTH': str(L),
                        'WIDTH': str(W),
                        'MODES': str(N),
                        'PPHW': str(self.pphw),
                        'GAMMA0': str(eta),
                        'NEUMANN': '0',
                        'N_FILE_BOUNDARY': str(N_file),
                        'BOUNDARY_UPPER': 'upper.boundary',
                        'BOUNDARY_LOWER': 'lower.boundary'}

        replace_in_file(self.template, self.xml, **replacements)
def get_loop_eigenfunction(N=1.05, eta=0.0, L=5., d=1., eps=0.05, nx=None,
                           loop_direction="+", loop_type='Bell', init_state='a',
                           init_phase=0.0, mpi=False, pphw=100,
                           effective_model_only=False,
                           neumann=1):
    """Return the instantaneous eigenfunctions and eigenvectors for each step
    in a parameter space loop.

        Parameters:
        -----------
            N: float
                Number of open modes (floor(N)).
            eta: float
                Dissipation strength.
            L: float
                System length.
            d: float
                System width.
            eps: float
                Half-maximum boundary roughness.
            nx: int
                Number of slices to calculate. If None, determine nx via
                pphw and N automatically.
            loop_direction: str
                Loop direction of the parameter space trajectory. Allowed
                values: + or -.
            loop_type: str
                Trajectory shape in parameter space.
            init_state: str
                Initial state of the evolution in the effective 2x2 system.
            init_phase: float
                Initial phase in the trajectory.
            mpi: bool
                Whether to use the parallel greens_code version.
            pphw: int
                Points per half-wavelength.
            effective_model_only: bool
                Whether to only calculate the effective model predictions.
            neumann: int
                Whether to use Neumann or Dirichlet boundary conditions.
    """

    greens_path = os.environ.get('GREENS_CODE_XML')
    XML = os.path.join(greens_path, "input_periodic_cell.xml")

    wg_kwargs = {'N': N,
                 'eta': eta,
                 'L': L,
                 'init_phase': init_phase,
                 'init_state': init_state,
                 'loop_direction': loop_direction,
                 'loop_type': loop_type,
                 'x_R0': eps
    }
    if neumann:
        WG = Neumann(**wg_kwargs)
    else:
        WG = DirichletReduced(**wg_kwargs)
    _, b0, b1 = WG.solve_ODE()

    # prepare waveguide and profile -------------------------------------------
    profile_kwargs = {'eps': eps,
                      'pphw': pphw,
                      'input_xml': XML,
                      'custom_directory': os.getcwd(),
                      'neumann': neumann}
    profile_kwargs.update(wg_kwargs)

    ep.profile.Generate_Profiles(**profile_kwargs)

    for file in glob.glob("N_*profile"):
        if "lower" in file:
            shutil.move(file, "boundary.lower_profile")
        if "upper" in file:
            shutil.move(file, "boundary.upper_profile")
    # -------------------------------------------------------------------------

    # trajectories ------------------------------------------------------------
    if 0:
        f, (ax1, ax2) = plt.subplots(nrows=2)
        ax1.semilogy(WG.t, abs(b0), "r-")
        ax1.semilogy(WG.t, abs(b1), "g-")

        wg_kwargs['loop_direction'] = '+'
        if neumann:
            WG = Neumann(**wg_kwargs)
        else:
            WG = DirichletReduced(**wg_kwargs)
        _, b0, b1 = WG.solve_ODE()

        ax1.semilogy(WG.t, abs(b0[::-1]), "r--")
        ax1.semilogy(WG.t, abs(b1[::-1]), "g--")

        ax2.plot(WG.t, WG.eVals[:,0].real, "r-")
        ax2.plot(WG.t, WG.eVals[:,1].real, "g-")
        ax2.plot(WG.t, WG.eVals[:,0].imag, "r--")
        ax2.plot(WG.t, WG.eVals[:,1].imag, "g--")
        # plt.savefig("evals_trajectories.png")
        plt.show()
    # -------------------------------------------------------------------------

    # change nx and ny accoring to pphw and modes -----------------------------
    nyout = pphw*N
    if nx is None:
        nx = int(L*(nyout+1.))
        print "nx:", nx
    ny = int(d*(nyout+1.))
    # -------------------------------------------------------------------------

    x = np.linspace(0, L, nx)
    y = np.linspace(0, d, ny)
    eps, delta = WG.get_cycle_parameters(x)

    K_0, K_1, Chi_0, Chi_1 = [ list() for n in range(4) ]

    job_kwargs = {'eta': eta,
                  'pphw': pphw,
                  'XML': XML,
                  'N': N,
                  'WG': WG,
                  'loop_direction': loop_direction,
                  'neumann': neumann}


    if not effective_model_only:
        # serialized version:
        results = []
        for n, (xn, epsn, deltan) in enumerate(zip(x, eps, delta)):
            run_single_job(n, xn, epsn, deltan, **job_kwargs)

        # pool = multiprocessing.Pool(processes=multiprocessing.cpu_count())
        # results = [ pool.apply_async(run_single_job, args=(n, xn, epsn, deltan),
        #                              kwds=job_kwargs)
        #             for n, (xn, epsn, deltan) in enumerate(zip(x, eps, delta)) ]
        # results = [ p.get() for p in results ]

        # alternative parallelization:
        # job_list = [ (n, xn, epsn, deltan, eta, pphw, XML, N, WG, loop_direction)
        #               for n, (xn, epsn, deltan) in enumerate(zip(x, eps, delta)) ]
        # results = pool.map(run_single_job, job_list)

        # properly unpack results
        for res in results:
            if res is None:
                # reuse last set of eigenvalues/eigenvectors if something went
                # wrong in the function call of run_single_job
                print "Warning: calculation failed."
            else:
                K0, K1, ev0, ev1 = res
            K_0.append(K0)
            K_1.append(K1)
            Chi_0.append(ev0)
            Chi_1.append(ev1)

        # -------------------------------------------------------------------------
        # numerical data

        K_0, K_1, Chi_0, Chi_1 = [ np.asarray(z) for z in K_0, K_1, Chi_0, Chi_1 ]

        # smooth
        K_0, K_1, Chi_0, Chi_1 = smooth_eigensystem(K_0, K_1, Chi_0, Chi_1,
                                                    eps=WG.x_R0, plot=False)

        # transpose array!
        Chi_0, Chi_1 = [ np.array(c).T for c in Chi_0, Chi_1 ]

        # unwrapp phase
        G = delta + WG.kr
        L_range = 2*np.pi/G  # make small error since L != r_nx*dx
        K_0 = np.unwrap(K_0.real*L_range)/L_range + 1j*K_0.imag
        K_1 = np.unwrap(K_1.real*L_range)/L_range + 1j*K_1.imag

        # TODO: handle case for eps = 0.0
        # remove discontinuous second order derivative (if we cross the DP)
        # plt.plot(np.diff(np.abs(K_0), 2))
        # plt.show()

        # assemble eigenvectors
        Chi_0 *= np.exp(1j*K_0*x)
        Chi_1 *= np.exp(1j*K_1*x) * np.exp(-1j*WG.kr*x)
        # -------------------------------------------------------------------------

    # -------------------------------------------------------------------------
    # effective model predictons

    # get eigensystem
    Chi_0_eff, Chi_1_eff = WG.eVecs_r[:,:,0], WG.eVecs_r[:,:,1]
    # Chi_0_eff, Chi_1_eff = b0, b1
    K_0_eff, K_1_eff = WG.eVals[:,0], WG.eVals[:,1]

    # interpolate
    K_0_eff = (interp1d(WG.t, K_0_eff.real)(x) +
                1j*interp1d(WG.t, K_0_eff.imag)(x))
    K_1_eff = (interp1d(WG.t, K_1_eff.real)(x) +
                1j*interp1d(WG.t, K_1_eff.imag)(x))

    Chi_0_eff = [ (interp1d(WG.t, Chi_0_eff[:,n].real)(x) +
                    1j*interp1d(WG.t, Chi_0_eff[:,n].imag)(x)) for n in 0, 1 ]
    Chi_1_eff = [ (interp1d(WG.t, Chi_1_eff[:,n].real)(x) +
                    1j*interp1d(WG.t, Chi_1_eff[:,n].imag)(x)) for n in 0, 1 ]
    # Chi_0_eff = [ (interp1d(WG.t, Chi_0_eff[:].real)(x) +
    #                 1j*interp1d(WG.t, Chi_0_eff[:].imag)(x)) for n in 0, 1 ]
    # Chi_1_eff = [ (interp1d(WG.t, Chi_1_eff[:].real)(x) +
    #                 1j*interp1d(WG.t, Chi_1_eff[:].imag)(x)) for n in 0, 1 ]
    Chi_0_eff, Chi_1_eff = [ np.array(c).T for c in Chi_0_eff, Chi_1_eff ]


    # fold back
    G = delta + WG.kr
    L_range = 2*np.pi/G  # make small error since L != r_nx*dx

    # np.savetxt("output_data.dat", zip(G, L_range, K_0_eff.real, K_1_eff.real,
    #                                   K_0.real, K_1.real),
    #            header='G L_range K_0_eff K_1_eff K_0 K_1')
    K_0_eff = ((-K_0_eff.real + G/2.) % G - G/2.) + 1j*K_0_eff.imag
    K_1_eff = ((-K_1_eff.real + G/2.) % G - G/2.) + 1j*K_1_eff.imag

    # unwrapp phase
    K_0_eff = np.unwrap(K_0_eff.real*L_range)/L_range + 1j*K_0_eff.imag
    K_1_eff = np.unwrap(K_1_eff.real*L_range)/L_range + 1j*K_1_eff.imag

    # assemble effective model eigenvectors
    Chi_0_eff[:,0] *= np.exp(1j*K_0_eff*x)
    Chi_0_eff[:,1] *= np.exp(1j*K_0_eff*x)
    Chi_1_eff[:,0] *= np.exp(1j*K_1_eff*x)
    Chi_1_eff[:,1] *= np.exp(1j*K_1_eff*x)

    # no additional factor of np.exp(-i*kr*x) since we unwrap the phase,
    # corresponding to K_n -> K_n + n*G
    if neumann:
        Chi_0_eff_0 = np.outer(Chi_0_eff[:,0], 1.*np.ones_like(y))
        Chi_0_eff_1 = np.outer(Chi_0_eff[:,1], #*np.exp(-1j*WG.kr*x),
                            np.sqrt(2.*WG.k0/WG.k1)*np.cos(np.pi*y))
        Chi_0_eff = Chi_0_eff_0 + Chi_0_eff_1

        Chi_1_eff_0 = np.outer(Chi_1_eff[:,0], 1.*np.ones_like(y))
        Chi_1_eff_1 = np.outer(Chi_1_eff[:,1], #*np.exp(-1j*WG.kr*x),
                            np.sqrt(2.*WG.k0/WG.k1)*np.cos(np.pi*y))
        Chi_1_eff = Chi_1_eff_0 + Chi_1_eff_1
    else:
        Chi_0_eff_0 = np.outer(Chi_0_eff[:,0], np.sin(np.pi*y))
        Chi_0_eff_1 = np.outer(Chi_0_eff[:,1], #*np.exp(-1j*WG.kr*x),
                            np.sqrt(WG.k1/WG.k0)*np.sin(2*np.pi*y))
        Chi_0_eff = Chi_0_eff_0 + Chi_0_eff_1

        Chi_1_eff_0 = np.outer(Chi_1_eff[:,0], np.sin(np.pi*y))
        Chi_1_eff_1 = np.outer(Chi_1_eff[:,1], #*np.exp(-1j*WG.kr*x),
                            np.sqrt(WG.k1/WG.k0)*np.sin(2*np.pi*y))
        Chi_1_eff = Chi_1_eff_0 + Chi_1_eff_1

    Chi_0_eff, Chi_1_eff = [ c.T for c in Chi_0_eff, Chi_1_eff ]
    # -------------------------------------------------------------------------

    # eigenvalues -------------------------------------------------------------
    if 1:
        plt.clf()
        f, (ax1, ax2, ax3) = plt.subplots(nrows=3, figsize=(8,6), dpi=80)
        prop = {'size': 12}

        if not effective_model_only:
            ax1.set_title(r"Numerical eigenvalues $K_n$")
            ax1.plot(x, K_0.real, "r-", label=r"$\Re{K_0}$")
            ax1.plot(x, K_0.imag, "b-", label=r"$\Im{K_0}$")
            ax1.plot(x, K_1.real, "r--", label=r"$\Re{K_1}$")
            ax1.plot(x, K_1.imag, "b--", label=r"$\Im{K_1}$")
            ax1.set_xlabel(r"$x$")
            l1 = ax1.legend(bbox_to_anchor=(1.3,1.075), prop=prop)

        ax2.set_title(r"Effective model eigenvalues $K^{\mathrm{eff}}_n$")
        ax2.plot(x, K_0_eff.real, "r-", label=r"$\Re{K^{\mathrm{eff}}_0}$")
        ax2.plot(x, K_0_eff.imag, "b-", label=r"$\Im{K^{\mathrm{eff}}_0}$")
        ax2.plot(x, K_1_eff.real, "r--", label=r"$\Re{K^{\mathrm{eff}}_1}$")
        ax2.plot(x, K_1_eff.imag, "b--", label=r"$\Im{K^{\mathrm{eff}}_1}$")
        ax2.set_xlabel(r"$x$")
        l2 = ax2.legend(bbox_to_anchor=(1.3,1.075), prop=prop)
        extra_artist=[l2]

        if not effective_model_only:
            ax3.set_title("Comparison")
            ax3.plot(x, abs(K_0 - K_0_eff)**2, "k-", label=r"$|K_0 - K^{\mathrm{eff}}_0|^2$")
            ax3.plot(x, abs(K_1 - K_1_eff)**2, "k--", label=r"$|K_1 - K^{\mathrm{eff}}_1|^2$")
            ax3.set_xlabel(r"$x$")
            l3 = ax3.legend(bbox_to_anchor=(1.3,1.075), prop=prop)
            extra_artist=[l3]

        plt.tight_layout()
        plt.savefig("eigenvalues.png", bbox_extra_artists=(extra_artist), 
                    bbox_inches='tight')
    # ------------------------------------------------------------------------

    # save potential ----------------------------------------------------------
    # for n, c in enumerate((Chi_0, Chi_1)):
    #     c = np.abs(c).flatten(order='F')
    #     nfile = range(len(c))
    #     np.savetxt("potential_imag_{}.dat".format(n),
    #               zip(nfile, c), fmt='%i %10.6f')
    #     c = (c.max() - c)/c.max()
    #     np.savetxt("potential_imag_{}_normalized.dat".format(n),
    #                zip(nfile, c), fmt='%i %10.6f')

    wavefunctions = [Chi_0_eff, Chi_1_eff]
    names = ["Chi_0_eff", "Chi_1_eff"]
    if not effective_model_only:
        wavefunctions += [Chi_0, Chi_1]
        names += ["Chi_0", "Chi_1"]

    for n, c in zip(names, wavefunctions):
        np.savetxt("potential_{}.dat".format(n), c)

        c = np.abs(c).flatten(order='F')
        nfile = range(len(c))
        np.savetxt("potential_{}_imag.dat".format(n),
                   zip(nfile, c), fmt='%i %10.6f')
        c = (c.max() - c)/c.max()
        np.savetxt("potential_{}_imag_normalized.dat".format(n),
                   zip(nfile, c), fmt='%i %10.6f')
    # -------------------------------------------------------------------------

    X, Y = np.meshgrid(x, y)
    for part in np.abs, np.angle, np.real, np.imag:
        if part == np.abs:
            cmap = 'Reds'
        else:
            cmap = 'RdBu_r'

        if not effective_model_only:
            plt.clf()
            Z = part(Chi_0)
            p = plt.pcolormesh(X, Y, Z, cmap=cmap)
            plt.colorbar(p)
            plt.savefig("Chi_0_{0.__name__}.png".format(part))

            plt.clf()
            Z = part(Chi_1)
            p = plt.pcolormesh(X, Y, Z, cmap=cmap)
            plt.colorbar(p)
            plt.savefig("Chi_1_{0.__name__}.png".format(part))

        plt.clf()
        Z_eff = part(Chi_0_eff)
        p = plt.pcolormesh(X, Y[::-1,:], Z_eff, cmap=cmap)
        plt.colorbar(p)
        plt.savefig("Chi_0_eff_{0.__name__}.png".format(part))

        plt.clf()
        Z_eff = part(-1j*Chi_1_eff)
        p = plt.pcolormesh(X, Y[::-1,:], Z_eff, cmap=cmap)
        plt.colorbar(p)
        plt.savefig("Chi_1_eff_{0.__name__}.png".format(part))