Exemple #1
0
import matplotlib.pyplot as plt
import numpy as np
import os
from scipy.interpolate import interp1d
try:
    import meep as mp
except:
    print(
        "Meep functions not available. Don't worry! You can still use everything else"
    )
import v_save as vs
import v_utilities as vu

syshome = vs.get_sys_home()
home = vs.get_home()

#%% MEEP MEDIUM IMPORT


def import_medium(name, from_um_factor=1, paper="R"):
    """Returns Medium instance from string with specified length scale
    
    It's widely based on Meep Materials Library, merely adapted to change 
    according to the length unit scale used.
    
    Parameters
    ----------
    name: str
        Name of the desired material.
    from_um_factor=1 : int, float, optional
Exemple #2
0
def main(from_um_factor, resolution_wlen, courant, submerged_index,
         displacement, surface_index, wlen_center, wlen_width, nfreq,
         air_wlen_factor, pml_wlen_factor, flux_wlen_factor, time_factor_cell,
         second_time_factor, series, folder, parallel, n_processes,
         split_chunks_evenly):

    #%% CLASSIC INPUT PARAMETERS
    """
    # Simulation size
    from_um_factor = 100e-3 # Conversion of 1 μm to my length unit (=100nm/1μm)
    resolution_wlen = 20 # >=8 pixels per smallest wavelength, i.e. np.floor(8/wvl_min)
    courant = 0.5
    
    # Nanoparticle specifications: Sphere in Vacuum :)
    paper = "R"
    reference = "Meep"
    displacement = 0 # Displacement of the surface from the bottom of the sphere in nm
    submerged_index = 1.33 # 1.33 for water
    surface_index = 1.54 # 1.54 for glass
    
    # Frequency and wavelength
    wlen_center = 650 # Main wavelength range in nm
    wlen_width = 200 # Wavelength band in nm
    nfreq = 100
    
    # Box dimensions
    pml_wlen_factor = 0.38
    air_wlen_factor = 0.15
    flux_wlen_factor = 0.1
    
    # Simulation time
    time_factor_cell = 1.2
    second_time_factor = 10
    
    # Saving directories
    series = "1stTest"
    folder = "Test/TestDipole/DipoleGlass"
    
    # Configuration
    parallel = False
    n_processes = 1
    split_chunks_evenly = True
    """

    #%% MORE INPUT PARAMETERS

    # Simulation size
    resolution = (from_um_factor * 1e3) * resolution_wlen / (wlen_center +
                                                             wlen_width / 2)
    resolution = int(np.ceil(resolution / 2)) * 2

    # Frequency and wavelength
    cutoff = 3.2  # Gaussian planewave source's parameter of shape
    nazimuthal = 16
    npolar = 20

    ### TREATED INPUT PARAMETERS

    # Nanoparticle specifications: Sphere in Vacuum :)
    displacement = displacement / (from_um_factor * 1e3)  # Now in Meep units

    # Frequency and wavelength
    wlen_center = wlen_center / (from_um_factor * 1e3)  # Now in Meep units
    wlen_width = wlen_width / (from_um_factor * 1e3)  # Now in Meep units
    freq_center = 1 / wlen_center  # Hz center frequency in Meep units
    freq_width = 1 / wlen_width  # Hz range in Meep units from highest to lowest

    # Space configuration
    pml_width = pml_wlen_factor * (wlen_center + wlen_width / 2
                                   )  # 0.5 * max(wlen_range)
    air_width = air_wlen_factor * (wlen_center + wlen_width / 2)
    flux_box_size = flux_wlen_factor * (wlen_center + wlen_width / 2)

    # Computation
    enlapsed = []
    if parallel:
        np_process = mp.count_processors()
    else:
        np_process = 1

    # Saving directories
    if series is None:
        series = "Test"
    if folder is None:
        folder = "Test"
    params_list = [
        "from_um_factor", "resolution_wlen", "resolution", "courant",
        "submerged_index", "displacement", "surface_index", "wlen_center",
        "wlen_width", "cutoff", "nfreq", "nazimuthal", "npolar",
        "flux_box_size", "cell_width", "pml_width", "air_width",
        "until_after_sources", "time_factor_cell", "second_time_factor",
        "enlapsed", "parallel", "n_processes", "split_chunks_evenly", "script",
        "sysname", "path"
    ]

    #%% GENERAL GEOMETRY SETUP

    air_width = air_width - air_width % (1 / resolution)

    pml_width = pml_width - pml_width % (1 / resolution)
    pml_layers = [mp.PML(thickness=pml_width)]

    # symmetries = [mp.Mirror(mp.Y),
    #               mp.Mirror(mp.Z, phase=-1)]
    # Two mirror planes reduce cell size to 1/4
    # Issue related that lead me to comment this lines:
    # https://github.com/NanoComp/meep/issues/1484

    cell_width = 2 * (pml_width + air_width)
    cell_width = cell_width - cell_width % (1 / resolution)
    cell_size = mp.Vector3(cell_width, cell_width, cell_width)

    # surface_center = r/4 - displacement/2 + cell_width/4
    # surface_center = surface_center - surface_center%(1/resolution)
    # displacement = r/2 + cell_width/2 - 2*surface_center

    displacement = displacement - displacement % (1 / resolution)

    flux_box_size = flux_box_size - flux_box_size % (1 / resolution)

    sources = [
        mp.Source(mp.GaussianSource(freq_center,
                                    fwidth=freq_width,
                                    is_integrated=True,
                                    cutoff=cutoff),
                  center=mp.Vector3(),
                  component=mp.Ez)
    ]
    # Ez-polarized point pulse
    # (its size parameter is null and it is centered at zero)
    # >> The planewave source extends into the PML
    # ==> is_integrated=True must be specified

    until_after_sources = time_factor_cell * cell_width * submerged_index
    # Enough time for the pulse to pass through all the cell
    # Originally: Aprox 3 periods of lowest frequency, using T=λ/c=λ in Meep units
    # Now: Aprox 3 periods of highest frequency, using T=λ/c=λ in Meep units

    # if surface_index != 1:
    #     geometry = [mp.Block(material=mp.Medium(index=surface_index),
    #                          center=mp.Vector3(
    #                              - displacement/2 + cell_width/4,
    #                              0, 0),
    #                          size=mp.Vector3(
    #                              cell_width/2 + displacement,
    #                              cell_width, cell_width))]
    # else:
    #     geometry = []
    # A certain material surface underneath it

    home = vs.get_home()
    sysname = vs.get_sys_name()
    path = os.path.join(home, folder, series)
    if not os.path.isdir(path) and vm.parallel_assign(0, n_processes,
                                                      parallel):
        os.makedirs(path)
    file = lambda f: os.path.join(path, f)

    #%% BASE SIMULATION: SETUP

    measure_ram()

    params = {}
    for p in params_list:
        params[p] = eval(p)

    sim = mp.Simulation(resolution=resolution,
                        cell_size=cell_size,
                        boundary_layers=pml_layers,
                        sources=sources,
                        k_point=mp.Vector3(),
                        Courant=courant,
                        default_material=mp.Medium(index=submerged_index),
                        output_single_precision=True,
                        split_chunks_evenly=split_chunks_evenly)

    measure_ram()

    near2far_box = sim.add_near2far(
        freq_center, freq_width, nfreq,
        mp.Near2FarRegion(center=mp.Vector3(x=-flux_box_size / 2),
                          size=mp.Vector3(0, flux_box_size, flux_box_size),
                          weight=-1),
        mp.Near2FarRegion(center=mp.Vector3(x=+flux_box_size / 2),
                          size=mp.Vector3(0, flux_box_size, flux_box_size),
                          weight=+1),
        mp.Near2FarRegion(center=mp.Vector3(y=-flux_box_size / 2),
                          size=mp.Vector3(flux_box_size, 0, flux_box_size),
                          weight=-1),
        mp.Near2FarRegion(center=mp.Vector3(y=+flux_box_size / 2),
                          size=mp.Vector3(flux_box_size, 0, flux_box_size),
                          weight=+1),
        mp.Near2FarRegion(center=mp.Vector3(z=-flux_box_size / 2),
                          size=mp.Vector3(flux_box_size, flux_box_size, 0),
                          weight=-1),
        mp.Near2FarRegion(center=mp.Vector3(z=+flux_box_size / 2),
                          size=mp.Vector3(flux_box_size, flux_box_size, 0),
                          weight=+1))
    measure_ram()
    # used_ram.append(used_ram[-1])

    #%% BASE SIMULATION: INITIALIZE

    temp = time()
    sim.init_sim()
    enlapsed.append(time() - temp)
    measure_ram()

    #%% BASE SIMULATION: SIMULATION :D

    step_ram_function = lambda sim: measure_ram()

    temp = time()
    sim.run(mp.at_beginning(step_ram_function),
            mp.at_time(int(second_time_factor * until_after_sources / 2),
                       step_ram_function),
            mp.at_end(step_ram_function),
            until_after_sources=second_time_factor * until_after_sources)
    #     mp.stop_when_fields_decayed(
    # np.mean(wlen_range), # dT = mean period of source
    # mp.Ez, # Component of field to check
    # mp.Vector3(0.5*cell_width - pml_width, 0, 0), # Where to check
    # 1e-3)) # Factor to decay
    enlapsed.append(time() - temp)
    del temp
    # Aprox 30 periods of lowest frequency, using T=λ/c=λ in Meep units

    #%% BASE SIMULATION: ANGULAR PATTERN ANALYSIS

    freqs = np.linspace(freq_center - freq_center / 2,
                        freq_center + freq_width / 2, nfreq)
    wlens = 1 / freqs

    # fraunhofer_distance = 8 * (r**2) / min(wlen_range)
    # radial_distance = max( 10 * fraunhofer_distance , 1.5 * cell_width/2 )
    # radius of far-field circle must be at least Fraunhofer distance

    radial_distance = cell_width
    azimuthal_angle = np.arange(0, 2 + 2 / nazimuthal,
                                2 / nazimuthal)  # in multiples of pi
    polar_angle = np.arange(0, 1 + 1 / npolar, 1 / npolar)

    poynting_x = []
    poynting_y = []
    poynting_z = []
    poynting_r = []

    for phi in azimuthal_angle:

        poynting_x.append([])
        poynting_y.append([])
        poynting_z.append([])
        poynting_r.append([])

        for theta in polar_angle:

            farfield_dict = sim.get_farfields(
                near2far_box,
                1,
                where=mp.Volume(center=mp.Vector3(
                    radial_distance * np.cos(np.pi * phi) *
                    np.sin(np.pi * theta),
                    radial_distance * np.sin(np.pi * phi) *
                    np.sin(np.pi *
                           theta), radial_distance * np.cos(np.pi * theta))))

            Px = farfield_dict["Ey"] * np.conjugate(farfield_dict["Hz"])
            Px -= farfield_dict["Ez"] * np.conjugate(farfield_dict["Hy"])
            Py = farfield_dict["Ez"] * np.conjugate(farfield_dict["Hx"])
            Py -= farfield_dict["Ex"] * np.conjugate(farfield_dict["Hz"])
            Pz = farfield_dict["Ex"] * np.conjugate(farfield_dict["Hy"])
            Pz -= farfield_dict["Ey"] * np.conjugate(farfield_dict["Hx"])

            Px = np.real(Px)
            Py = np.real(Py)
            Pz = np.real(Pz)

            poynting_x[-1].append(Px)
            poynting_y[-1].append(Py)
            poynting_z[-1].append(Pz)
            poynting_r[-1].append(
                np.sqrt(np.square(Px) + np.square(Py) + np.square(Pz)))

    del phi, theta, farfield_dict, Px, Py, Pz

    poynting_x = np.array(poynting_x)
    poynting_y = np.array(poynting_y)
    poynting_z = np.array(poynting_z)
    poynting_r = np.array(poynting_r)

    #%% BASE SIMULATION: SAVE DATA

    for p in params_list:
        params[p] = eval(p)

    os.chdir(path)
    sim.save_near2far("BaseNear2Far", near2far_box)
    if vm.parallel_assign(0, np_process, parallel):
        f = h5.File("BaseNear2Far.h5", "r+")
        for key, par in params.items():
            f[list(f.keys())[0]].attrs[key] = par
        f.close()
        del f
    os.chdir(syshome)

    if vm.parallel_assign(1, np_process, parallel):
        f = h5.File(file("BaseResults.h5"), "w")
        f["Px"] = poynting_x
        f["Py"] = poynting_y
        f["Pz"] = poynting_z
        f["Pr"] = poynting_r
        for dset in f.values():
            for key, par in params.items():
                dset.attrs[key] = par
        f.close()
        del f

    # if not split_chunks_evenly:
    #     vm.save_chunks(sim, params, path)

    if parallel:
        f = h5.File(file("BaseRAM.h5"),
                    "w",
                    driver='mpio',
                    comm=MPI.COMM_WORLD)
        current_process = mp.my_rank()
        f.create_dataset("RAM", (len(used_ram), np_process), dtype="float")
        f["RAM"][:, current_process] = used_ram
        for a in params:
            f["RAM"].attrs[a] = params[a]
        f.create_dataset("SWAP", (len(used_ram), np_process), dtype="int")
        f["SWAP"][:, current_process] = swapped_ram
        for a in params:
            f["SWAP"].attrs[a] = params[a]
    else:
        f = h5.File(file("BaseRAM.h5"), "w")
        f.create_dataset("RAM", data=used_ram)
        for a in params:
            f["RAM"].attrs[a] = params[a]
        f.create_dataset("SWAP", data=swapped_ram)
        for a in params:
            f["SWAP"].attrs[a] = params[a]
    f.close()
    del f

    sim.reset_meep()

    #%% FURTHER SIMULATION

    if surface_index != 1:

        #% FURTHER SIMULATION: SETUP

        measure_ram()

        params = {}
        for p in params_list:
            params[p] = eval(p)

        sim = mp.Simulation(resolution=resolution,
                            cell_size=cell_size,
                            boundary_layers=pml_layers,
                            sources=sources,
                            k_point=mp.Vector3(),
                            Courant=courant,
                            default_material=mp.Medium(index=surface_index),
                            output_single_precision=True,
                            split_chunks_evenly=split_chunks_evenly)

        measure_ram()

        near2far_box = sim.add_near2far(
            freq_center, freq_width, nfreq,
            mp.Near2FarRegion(center=mp.Vector3(x=-flux_box_size / 2),
                              size=mp.Vector3(0, flux_box_size, flux_box_size),
                              weight=-1),
            mp.Near2FarRegion(center=mp.Vector3(x=+flux_box_size / 2),
                              size=mp.Vector3(0, flux_box_size, flux_box_size),
                              weight=+1),
            mp.Near2FarRegion(center=mp.Vector3(y=-flux_box_size / 2),
                              size=mp.Vector3(flux_box_size, 0, flux_box_size),
                              weight=-1),
            mp.Near2FarRegion(center=mp.Vector3(y=+flux_box_size / 2),
                              size=mp.Vector3(flux_box_size, 0, flux_box_size),
                              weight=+1),
            mp.Near2FarRegion(center=mp.Vector3(z=-flux_box_size / 2),
                              size=mp.Vector3(flux_box_size, flux_box_size, 0),
                              weight=-1),
            mp.Near2FarRegion(center=mp.Vector3(z=+flux_box_size / 2),
                              size=mp.Vector3(flux_box_size, flux_box_size, 0),
                              weight=+1))
        measure_ram()
        # used_ram.append(used_ram[-1])

        #% FURTHER SIMULATION: INITIALIZE

        temp = time()
        sim.init_sim()
        enlapsed.append(time() - temp)
        measure_ram()

        #% FURTHER SIMULATION: SIMULATION :D

        step_ram_function = lambda sim: measure_ram()

        temp = time()
        sim.run(mp.at_beginning(step_ram_function),
                mp.at_time(int(second_time_factor * until_after_sources / 2),
                           step_ram_function),
                mp.at_end(step_ram_function),
                until_after_sources=second_time_factor * until_after_sources)
        #     mp.stop_when_fields_decayed(
        # np.mean(wlen_range), # dT = mean period of source
        # mp.Ez, # Component of field to check
        # mp.Vector3(0.5*cell_width - pml_width, 0, 0), # Where to check
        # 1e-3)) # Factor to decay
        enlapsed.append(time() - temp)
        del temp
        # Aprox 30 periods of lowest frequency, using T=λ/c=λ in Meep units

        #% FURTHER SIMULATION: ANGULAR PATTERN ANALYSIS

        poynting_x2 = []
        poynting_y2 = []
        poynting_z2 = []
        poynting_r2 = []

        for phi in azimuthal_angle:

            poynting_x2.append([])
            poynting_y2.append([])
            poynting_z2.append([])
            poynting_r2.append([])

            for theta in polar_angle:

                farfield_dict = sim.get_farfields(
                    near2far_box,
                    1,
                    where=mp.Volume(center=mp.Vector3(
                        radial_distance * np.cos(np.pi * phi) *
                        np.sin(np.pi * theta),
                        radial_distance * np.sin(np.pi * phi) *
                        np.sin(np.pi *
                               theta), radial_distance *
                        np.cos(np.pi * theta))))

                Px = farfield_dict["Ey"] * np.conjugate(farfield_dict["Hz"])
                Px -= farfield_dict["Ez"] * np.conjugate(farfield_dict["Hy"])
                Py = farfield_dict["Ez"] * np.conjugate(farfield_dict["Hx"])
                Py -= farfield_dict["Ex"] * np.conjugate(farfield_dict["Hz"])
                Pz = farfield_dict["Ex"] * np.conjugate(farfield_dict["Hy"])
                Pz -= farfield_dict["Ey"] * np.conjugate(farfield_dict["Hx"])

                Px = np.real(Px)
                Py = np.real(Py)
                Pz = np.real(Pz)

                poynting_x2[-1].append(Px)
                poynting_y2[-1].append(Py)
                poynting_z2[-1].append(Pz)
                poynting_r2[-1].append(
                    np.sqrt(np.square(Px) + np.square(Py) + np.square(Pz)))

        del phi, theta, farfield_dict, Px, Py, Pz

        poynting_x2 = np.array(poynting_x2)
        poynting_y2 = np.array(poynting_y2)
        poynting_z2 = np.array(poynting_z2)
        poynting_r2 = np.array(poynting_r2)

        #% FURTHER SIMULATION: SAVE DATA

        for p in params_list:
            params[p] = eval(p)

        os.chdir(path)
        sim.save_near2far("FurtherNear2Far", near2far_box)
        if vm.parallel_assign(0, np_process, parallel):
            f = h5.File("FurtherNear2Far.h5", "r+")
            for key, par in params.items():
                f[list(f.keys())[0]].attrs[key] = par
            f.close()
            del f
        os.chdir(syshome)

        if vm.parallel_assign(1, np_process, parallel):
            f = h5.File(file("FurtherResults.h5"), "w")
            f["Px"] = poynting_x2
            f["Py"] = poynting_y2
            f["Pz"] = poynting_z2
            f["Pr"] = poynting_r2
            for dset in f.values():
                for key, par in params.items():
                    dset.attrs[key] = par
            f.close()
            del f

        # if not split_chunks_evenly:
        #     vm.save_chunks(sim, params, path)

        if parallel:
            f = h5.File(file("FurtherRAM.h5"),
                        "w",
                        driver='mpio',
                        comm=MPI.COMM_WORLD)
            current_process = mp.my_rank()
            f.create_dataset("RAM", (len(used_ram), np_process), dtype="float")
            f["RAM"][:, current_process] = used_ram
            for a in params:
                f["RAM"].attrs[a] = params[a]
            f.create_dataset("SWAP", (len(used_ram), np_process), dtype="int")
            f["SWAP"][:, current_process] = swapped_ram
            for a in params:
                f["SWAP"].attrs[a] = params[a]
        else:
            f = h5.File(file("FurtherRAM.h5"), "w")
            f.create_dataset("RAM", data=used_ram)
            for a in params:
                f["RAM"].attrs[a] = params[a]
            f.create_dataset("SWAP", data=swapped_ram)
            for a in params:
                f["SWAP"].attrs[a] = params[a]
        f.close()
        del f

    #%% NORMALIZE AND REARANGE DATA

    if surface_index != 1:
        max_poynting_r = np.max(
            [np.max(np.abs(poynting_r)),
             np.max(np.abs(poynting_r2))])
    else:
        max_poynting_r = np.max(np.abs(poynting_r))

    poynting_x = np.array(poynting_x) / max_poynting_r
    poynting_y = np.array(poynting_y) / max_poynting_r
    poynting_z = np.array(poynting_z) / max_poynting_r
    poynting_r = np.array(poynting_r) / max_poynting_r

    if surface_index != 1:

        poynting_x0 = np.array(poynting_x)
        poynting_y0 = np.array(poynting_y)
        poynting_z0 = np.array(poynting_z)
        poynting_r0 = np.array(poynting_r)

        poynting_x2 = np.array(poynting_x2) / max_poynting_r
        poynting_y2 = np.array(poynting_y2) / max_poynting_r
        poynting_z2 = np.array(poynting_z2) / max_poynting_r
        poynting_r2 = np.array(poynting_r2) / max_poynting_r

    #%%
    """
    poynting_x = np.array(poynting_x0)
    poynting_y = np.array(poynting_y0)
    poynting_z = np.array(poynting_z0)
    poynting_r = np.array(poynting_r0)
    """

    if surface_index != 1:

        polar_limit = np.arcsin(displacement / radial_distance) + .5

        for i in range(len(polar_angle)):
            if polar_angle[i] > polar_limit:
                index_limit = i
                break

        poynting_x[:, index_limit:, :] = poynting_x2[:, index_limit:, :]
        poynting_y[:, index_limit:, :] = poynting_y2[:, index_limit:, :]
        poynting_z[:, index_limit:, :] = poynting_z2[:, index_limit:, :]
        poynting_r[:, index_limit:, :] = poynting_r2[:, index_limit:, :]

        poynting_x[:, index_limit - 1, :] = np.array([[
            np.mean([p2, p0]) for p2, p0 in zip(poy2, poy0)
        ] for poy2, poy0 in zip(poynting_x2[:, index_limit -
                                            1, :], poynting_x0[:, index_limit -
                                                               1, :])])
        poynting_y[:, index_limit - 1, :] = np.array([[
            np.mean([p2, p0]) for p2, p0 in zip(poy2, poy0)
        ] for poy2, poy0 in zip(poynting_y2[:, index_limit -
                                            1, :], poynting_y0[:, index_limit -
                                                               1, :])])
        poynting_z[:, index_limit - 1, :] = np.array([[
            np.mean([p2, p0]) for p2, p0 in zip(poy2, poy0)
        ] for poy2, poy0 in zip(poynting_z2[:, index_limit -
                                            1, :], poynting_z0[:, index_limit -
                                                               1, :])])
        poynting_r[:, index_limit - 1, :] = np.sqrt(
            np.squared(poynting_x[:, index_limit - 1, :]) +
            np.squared(poynting_y[:, index_limit - 1, :]) +
            np.squared(poynting_y[:, index_limit - 1, :]))

    #%% SAVE FINAL DATA

    if surface_index != 1 and vm.parallel_assign(0, np_process, parallel):

        os.remove(file("BaseRAM.h5"))
        os.rename(file("FurtherRAM.h5"), file("RAM.h5"))

    elif vm.parallel_assign(0, np_process, parallel):

        os.rename(file("BaseRAM.h5"), file("RAM.h5"))

    #%% PLOT ANGULAR PATTERN IN 3D

    if vm.parallel_assign(1, np_process, parallel):

        freq_index = np.argmin(np.abs(freqs - freq_center))

        fig = plt.figure()
        plt.suptitle(
            f'Angular Pattern of point dipole molecule at {from_um_factor * 1e3 * wlen_center:.0f} nm'
        )
        ax = fig.add_subplot(1, 1, 1, projection='3d')
        ax.plot_surface(poynting_x[:, :, freq_index],
                        poynting_y[:, :, freq_index],
                        poynting_z[:, :, freq_index],
                        cmap=plt.get_cmap('jet'),
                        linewidth=1,
                        antialiased=False,
                        alpha=0.5)
        ax.set_xlabel(r"$P_x$")
        ax.set_ylabel(r"$P_y$")
        ax.set_zlabel(r"$P_z$")

        plt.savefig(file("AngularPattern.png"))

    #%% PLOT ANGULAR PATTERN PROFILE FOR DIFFERENT POLAR ANGLES

    if vm.parallel_assign(0, np_process, parallel):

        freq_index = np.argmin(np.abs(freqs - freq_center))
        index = [
            list(polar_angle).index(alpha) for alpha in [0, .25, .5, .75, 1]
        ]

        plt.figure()
        plt.suptitle(
            f'Angular Pattern of point dipole molecule at {from_um_factor * 1e3 * wlen_center:.0f} nm'
        )
        ax_plain = plt.axes()
        for i in index:
            ax_plain.plot(poynting_x[:, i, freq_index],
                          poynting_y[:, i, freq_index],
                          ".-",
                          label=rf"$\theta$ = {polar_angle[i]:.2f} $\pi$")
        plt.legend()
        ax_plain.set_xlabel(r"$P_x$")
        ax_plain.set_ylabel(r"$P_y$")
        ax_plain.set_aspect("equal")

        plt.savefig(file("AngularPolar.png"))

    #%% PLOT ANGULAR PATTERN PROFILE FOR DIFFERENT AZIMUTHAL ANGLES

    if vm.parallel_assign(1, np_process, parallel):

        freq_index = np.argmin(np.abs(freqs - freq_center))
        index = [
            list(azimuthal_angle).index(alpha)
            for alpha in [0, .25, .5, .75, 1, 1.25, 1.5, 1.75, 2]
        ]

        plt.figure()
        plt.suptitle(
            f'Angular Pattern of point dipole molecule at {from_um_factor * 1e3 * wlen_center:.0f} nm'
        )
        ax_plain = plt.axes()
        for i in index:
            ax_plain.plot(poynting_x[i, :, freq_index],
                          poynting_z[i, :, freq_index],
                          ".-",
                          label=rf"$\phi$ = {azimuthal_angle[i]:.2f} $\pi$")
        plt.legend()
        ax_plain.set_xlabel(r"$P_x$")
        ax_plain.set_ylabel(r"$P_z$")

        plt.savefig(file("AngularAzimuthal.png"))

    if vm.parallel_assign(0, np_process, parallel):

        freq_index = np.argmin(np.abs(freqs - freq_center))
        index = [
            list(azimuthal_angle).index(alpha)
            for alpha in [0, .25, .5, .75, 1, 1.25, 1.5, 1.75, 2]
        ]

        plt.figure()
        plt.suptitle(
            f'Angular Pattern of point dipole molecule at {from_um_factor * 1e3 * wlen_center:.0f} nm'
        )
        ax_plain = plt.axes()
        for i in index:
            ax_plain.plot(np.sqrt(
                np.square(poynting_x[i, :, freq_index]) +
                np.square(poynting_y[i, :, freq_index])),
                          poynting_z[i, :, freq_index],
                          ".-",
                          label=rf"$\phi$ = {azimuthal_angle[i]:.2f} $\pi$")
        plt.legend()
        ax_plain.set_xlabel(r"$P_\rho$")
        ax_plain.set_ylabel(r"$P_z$")

        plt.savefig(file("AngularAzimuthalAbs.png"))
Exemple #3
0
def main(series, folder, resolution, from_um_factor, r, paper, wlen):
    #%% PARAMETERS

    # Au sphere
    r = r / (from_um_factor * 1e3)  # Radius of sphere now in Meep units
    medium = import_medium("Au", from_um_factor,
                           paper=paper)  # Medium of sphere: gold (Au)

    # Frequency and wavelength
    wlen = wlen / (from_um_factor * 1e3)  # Wavelength now in Meep units

    # Space configuration
    pml_width = 0.38 * wlen  # 0.5 * wlen
    air_width = r / 2  # 2 * r

    # Field Measurements
    period_line = 1
    period_plane = 1
    after_cell_run_time = 10 * wlen

    # Computation time
    enlapsed = []

    # Saving directories
    if series is None:
        series = f"AuSphereField{2*r*from_um_factor*1e3:.0f}WLen{wlen*from_um_factor*1e3:.0f}"
    if folder is None:
        folder = "AuMieSphere/AuSphereField"
    home = vs.get_home()

    #%% GENERAL GEOMETRY SETUP

    air_width = air_width - air_width % (1 / resolution)

    pml_width = pml_width - pml_width % (1 / resolution)
    pml_layers = [mp.PML(thickness=pml_width)]

    symmetries = [mp.Mirror(mp.Y), mp.Mirror(mp.Z, phase=-1)]
    # Cause of symmetry, two mirror planes reduce cell size to 1/4

    cell_width = 2 * (pml_width + air_width + r)
    cell_width = cell_width - cell_width % (1 / resolution)
    cell_size = mp.Vector3(cell_width, cell_width, cell_width)

    source_center = -0.5 * cell_width + pml_width
    print("Resto Source Center: {}".format(source_center % (1 / resolution)))
    sources = [
        mp.Source(mp.ContinuousSource(wavelength=wlen, is_integrated=True),
                  center=mp.Vector3(source_center),
                  size=mp.Vector3(0, cell_width, cell_width),
                  component=mp.Ez)
    ]
    # Ez-polarized monochromatic planewave
    # (its size parameter fills the entire cell in 2d)
    # >> The planewave source extends into the PML
    # ==> is_integrated=True must be specified

    geometry = [mp.Sphere(material=medium, center=mp.Vector3(), radius=r)]
    # Au sphere with frequency-dependant characteristics imported from Meep.

    path = os.path.join(home, folder, series)
    if not os.path.isdir(path): vs.new_dir(path)
    file = lambda f: os.path.join(path, f)

    #%% SAVE GET FUNCTIONS

    def get_line(sim):
        return sim.get_array(center=mp.Vector3(),
                             size=mp.Vector3(cell_width),
                             component=mp.Ez)

    def get_plane(sim):
        return sim.get_array(center=mp.Vector3(),
                             size=mp.Vector3(0, cell_width, cell_width),
                             component=mp.Ez)

    #%% INITIALIZE

    sim = mp.Simulation(resolution=resolution,
                        cell_size=cell_size,
                        boundary_layers=pml_layers,
                        sources=sources,
                        symmetries=symmetries,
                        geometry=geometry)

    temp = time()
    sim.init_sim()
    enlapsed.append(time() - temp)

    #%% DEFINE SAVE STEP FUNCTIONS

    f, save_line = vs.save_slice_generator(sim, file("Lines.h5"), "Ez",
                                           get_line)
    g, save_plane = vs.save_slice_generator(sim, file("Planes.h5"), "Ez",
                                            get_plane)

    to_do_while_running = [
        mp.at_every(period_line, save_line),
        mp.at_every(period_plane, save_plane)
    ]

    #%% RUN!

    temp = time()
    sim.run(*to_do_while_running, until=cell_width + after_cell_run_time)
    del f, g
    enlapsed.append(time() - temp)

    #%% SAVE METADATA

    params = dict(from_um_factor=from_um_factor,
                  resolution=resolution,
                  r=r,
                  paper=paper,
                  pml_width=pml_width,
                  air_width=air_width,
                  cell_width=cell_width,
                  source_center=source_center,
                  wlen=wlen,
                  period_line=period_line,
                  period_plane=period_plane,
                  after_cell_run_time=after_cell_run_time,
                  series=series,
                  folder=folder,
                  home=home,
                  enlapsed=enlapsed)

    f = h5.File(file("Lines.h5"), "r+")
    for a in params:
        f["Ez"].attrs[a] = params[a]
    f.close()
    del f

    g = h5.File(file("Planes.h5"), "r+")
    for a in params:
        g["Ez"].attrs[a] = params[a]
    g.close()
    del g

    sim.reset_meep()
def main(from_um_factor, resolution, courant, r, material, paper, reference,
         submerged_index, displacement, surface_index, wlen_range, nfreq,
         air_r_factor, pml_wlen_factor, flux_r_factor, time_factor_cell,
         second_time_factor, series, folder, parallel, n_processes, n_cores,
         n_nodes, split_chunks_evenly, load_flux, load_chunks, near2far):

    #%% CLASSIC INPUT PARAMETERS
    """
    # Simulation size
    from_um_factor = 10e-3 # Conversion of 1 μm to my length unit (=10nm/1μm)
    resolution = 2 # >=8 pixels per smallest wavelength, i.e. np.floor(8/wvl_min)
    courant = 0.5
    
    # Nanoparticle specifications: Sphere in Vacuum :)
    r = 51.5  # Radius of sphere in nm
    paper = "R"
    reference = "Meep"
    displacement = 0 # Displacement of the surface from the bottom of the sphere in nm
    submerged_index = 1 # 1.33 for water
    surface_index = 1 # 1.54 for glass
    
    # Frequency and wavelength
    wlen_range = np.array([350,500]) # Wavelength range in nm
    nfreq = 100
    
    # Box dimensions
    pml_wlen_factor = 0.38
    air_r_factor = 0.5
    flux_r_factor = 0 #0.1
    
    # Simulation time
    time_factor_cell = 1.2
    second_time_factor = 10
    
    # Saving directories
    series = "SilverRes4"
    folder = "Test/TestSilver"
    
    # Configuration
    parallel = False
    n_processes = 1
    n_cores = 1
    n_nodes = 1
    split_chunks_evenly = True
    load_flux = False
    load_chunks = True    
    near2far = False
    """

    #%% MORE INPUT PARAMETERS

    # Frequency and wavelength
    cutoff = 3.2  # Gaussian planewave source's parameter of shape
    nazimuthal = 16
    npolar = 20

    ### TREATED INPUT PARAMETERS

    # Nanoparticle specifications: Sphere in Vacuum :)
    r = r / (from_um_factor * 1e3)  # Now in Meep units
    if reference == "Meep":
        medium = vmt.import_medium(material,
                                   from_um_factor=from_um_factor,
                                   paper=paper)
        # Importing material constants dependant on frequency from Meep Library
    elif reference == "RIinfo":
        medium = vmt.MediumFromFile(material,
                                    paper=paper,
                                    reference=reference,
                                    from_um_factor=from_um_factor)
        # Importing material constants dependant on frequency from external file
    else:
        raise ValueError("Reference for medium not recognized. Sorry :/")
    displacement = displacement / (from_um_factor * 1e3)  # Now in Meep units

    # Frequency and wavelength
    wlen_range = np.array(wlen_range)
    wlen_range = wlen_range / (from_um_factor * 1e3)  # Now in Meep units
    freq_range = 1 / wlen_range  # Hz range in Meep units from highest to lowest
    freq_center = np.mean(freq_range)
    freq_width = max(freq_range) - min(freq_range)

    # Space configuration
    pml_width = pml_wlen_factor * max(wlen_range)  # 0.5 * max(wlen_range)
    air_width = air_r_factor * r  # 0.5 * max(wlen_range)
    flux_box_size = 2 * (1 + flux_r_factor) * r

    # Saving directories
    if series is None:
        series = "Test"
    if folder is None:
        folder = "Test"
    params_list = [
        "from_um_factor", "resolution", "courant", "material", "r", "paper",
        "reference", "submerged_index", "displacement", "surface_index",
        "wlen_range", "nfreq", "nazimuthal", "npolar", "cutoff",
        "flux_box_size", "cell_width", "pml_width", "air_width",
        "source_center", "until_after_sources", "time_factor_cell",
        "second_time_factor", "enlapsed", "parallel", "n_processes", "n_cores",
        "n_nodes", "split_chunks_evenly", "near2far", "script", "sysname",
        "path"
    ]

    #%% GENERAL GEOMETRY SETUP

    air_width = air_width - air_width % (1 / resolution)

    pml_width = pml_width - pml_width % (1 / resolution)
    pml_layers = [mp.PML(thickness=pml_width)]

    # symmetries = [mp.Mirror(mp.Y),
    #               mp.Mirror(mp.Z, phase=-1)]
    # Two mirror planes reduce cell size to 1/4
    # Issue related that lead me to comment this lines:
    # https://github.com/NanoComp/meep/issues/1484

    cell_width = 2 * (pml_width + air_width + r)
    cell_width = cell_width - cell_width % (1 / resolution)
    cell_size = mp.Vector3(cell_width, cell_width, cell_width)

    # surface_center = r/4 - displacement/2 + cell_width/4
    # surface_center = surface_center - surface_center%(1/resolution)
    # displacement = r/2 + cell_width/2 - 2*surface_center

    displacement = displacement - displacement % (1 / resolution)

    flux_box_size = flux_box_size - flux_box_size % (1 / resolution)

    source_center = -0.5 * cell_width + pml_width
    sources = [
        mp.Source(mp.GaussianSource(freq_center,
                                    fwidth=freq_width,
                                    is_integrated=True,
                                    cutoff=cutoff),
                  center=mp.Vector3(source_center),
                  size=mp.Vector3(0, cell_width, cell_width),
                  component=mp.Ez)
    ]
    # Ez-polarized planewave pulse
    # (its size parameter fills the entire cell in 2d)
    # >> The planewave source extends into the PML
    # ==> is_integrated=True must be specified

    until_after_sources = time_factor_cell * cell_width * submerged_index
    # Enough time for the pulse to pass through all the cell
    # Originally: Aprox 3 periods of lowest frequency, using T=λ/c=λ in Meep units
    # Now: Aprox 3 periods of highest frequency, using T=λ/c=λ in Meep units

    geometry = [mp.Sphere(material=medium, center=mp.Vector3(), radius=r)]
    # Au sphere with frequency-dependant characteristics imported from Meep.

    if surface_index != 1:
        geometry = [
            mp.Block(material=mp.Medium(index=surface_index),
                     center=mp.Vector3(
                         r / 2 - displacement / 2 + cell_width / 4, 0, 0),
                     size=mp.Vector3(cell_width / 2 - r + displacement,
                                     cell_width, cell_width)), *geometry
        ]
    # A certain material surface underneath it

    home = vs.get_home()
    sysname = vs.get_sys_name()
    path = os.path.join(home, folder, series)
    if not os.path.isdir(path) and vm.parallel_assign(0, n_processes,
                                                      parallel):
        os.makedirs(path)
    file = lambda f: os.path.join(path, f)

    # Computation
    enlapsed = []

    parallel_specs = np.array([n_processes, n_cores, n_nodes], dtype=int)
    max_index = np.argmax(parallel_specs)
    for index, item in enumerate(parallel_specs):
        if item == 0: parallel_specs[index] = 1
    parallel_specs[0:max_index] = np.full(parallel_specs[0:max_index].shape,
                                          max(parallel_specs))
    n_processes, n_cores, n_nodes = parallel_specs
    parallel = max(parallel_specs) > 1
    del parallel_specs, max_index, index, item

    if parallel:
        np_process = mp.count_processors()
    else:
        np_process = 1

    #%% FIRST RUN

    measure_ram()

    params = {}
    for p in params_list:
        params[p] = eval(p)

    stable, max_courant = vm.check_stability(params)
    if stable:
        print("As a whole, the simulation should be stable")
    else:
        print("As a whole, the simulation could not be stable")
        print(f"Recommended maximum courant factor is {max_courant}")

    if load_flux:
        try:
            flux_path = vm.check_midflux(params)[0]
            flux_needed = False
        except:
            flux_needed = True
    else:
        flux_needed = True

    if load_chunks and not split_chunks_evenly:
        try:
            chunks_path = vm.check_chunks(params)[0]
            chunk_layout = os.path.join(chunks_path, "Layout.h5")
            chunks_needed = False
        except:
            chunks_needed = True
            flux_needed = True
    else:
        if not split_chunks_evenly:
            chunks_needed = True
            flux_needed = True
        else:
            chunk_layout = None
            chunks_needed = False

    if chunks_needed:

        sim = mp.Simulation(
            resolution=resolution,
            cell_size=cell_size,
            boundary_layers=pml_layers,
            sources=sources,
            k_point=mp.Vector3(),
            Courant=courant,
            default_material=mp.Medium(index=submerged_index),
            output_single_precision=True,
            split_chunks_evenly=split_chunks_evenly,
            # symmetries=symmetries,
            geometry=geometry)

        sim.init_sim()

        chunks_path = vm.save_chunks(sim, params, path)
        chunk_layout = os.path.join(chunks_path, "Layout.h5")

        del sim

    if flux_needed:

        #% FIRST RUN: SET UP

        measure_ram()

        sim = mp.Simulation(resolution=resolution,
                            cell_size=cell_size,
                            boundary_layers=pml_layers,
                            sources=sources,
                            k_point=mp.Vector3(),
                            Courant=courant,
                            default_material=mp.Medium(index=submerged_index),
                            split_chunks_evenly=split_chunks_evenly,
                            chunk_layout=chunk_layout,
                            output_single_precision=True)  #,
        # symmetries=symmetries)
        # >> k_point zero specifies boundary conditions needed
        # for the source to be infinitely extended

        measure_ram()

        # Scattered power --> Computed by surrounding it with closed DFT flux box
        # (its size and orientation are irrelevant because of Poynting's theorem)
        box_x1 = sim.add_flux(
            freq_center, freq_width, nfreq,
            mp.FluxRegion(center=mp.Vector3(x=-flux_box_size / 2),
                          size=mp.Vector3(0, flux_box_size, flux_box_size)))
        box_x2 = sim.add_flux(
            freq_center, freq_width, nfreq,
            mp.FluxRegion(center=mp.Vector3(x=+flux_box_size / 2),
                          size=mp.Vector3(0, flux_box_size, flux_box_size)))
        box_y1 = sim.add_flux(
            freq_center, freq_width, nfreq,
            mp.FluxRegion(center=mp.Vector3(y=-flux_box_size / 2),
                          size=mp.Vector3(flux_box_size, 0, flux_box_size)))
        box_y2 = sim.add_flux(
            freq_center, freq_width, nfreq,
            mp.FluxRegion(center=mp.Vector3(y=+flux_box_size / 2),
                          size=mp.Vector3(flux_box_size, 0, flux_box_size)))
        box_z1 = sim.add_flux(
            freq_center, freq_width, nfreq,
            mp.FluxRegion(center=mp.Vector3(z=-flux_box_size / 2),
                          size=mp.Vector3(flux_box_size, flux_box_size, 0)))
        box_z2 = sim.add_flux(
            freq_center, freq_width, nfreq,
            mp.FluxRegion(center=mp.Vector3(z=+flux_box_size / 2),
                          size=mp.Vector3(flux_box_size, flux_box_size, 0)))
        # Funny you can encase the sphere (r radius) so closely (2r-sided box)

        measure_ram()

        if near2far:
            near2far_box = sim.add_near2far(
                freq_center, freq_width, nfreq,
                mp.Near2FarRegion(center=mp.Vector3(x=-flux_box_size / 2),
                                  size=mp.Vector3(0, flux_box_size,
                                                  flux_box_size),
                                  weight=-1),
                mp.Near2FarRegion(center=mp.Vector3(x=+flux_box_size / 2),
                                  size=mp.Vector3(0, flux_box_size,
                                                  flux_box_size),
                                  weight=+1),
                mp.Near2FarRegion(center=mp.Vector3(y=-flux_box_size / 2),
                                  size=mp.Vector3(flux_box_size, 0,
                                                  flux_box_size),
                                  weight=-1),
                mp.Near2FarRegion(center=mp.Vector3(y=+flux_box_size / 2),
                                  size=mp.Vector3(flux_box_size, 0,
                                                  flux_box_size),
                                  weight=+1),
                mp.Near2FarRegion(center=mp.Vector3(z=-flux_box_size / 2),
                                  size=mp.Vector3(flux_box_size, flux_box_size,
                                                  0),
                                  weight=-1),
                mp.Near2FarRegion(center=mp.Vector3(z=+flux_box_size / 2),
                                  size=mp.Vector3(flux_box_size, flux_box_size,
                                                  0),
                                  weight=+1))
            measure_ram()
        else:
            near2far_box = None
            # used_ram.append(used_ram[-1])

        #% FIRST RUN: INITIALIZE

        temp = time()
        sim.init_sim()
        enlapsed.append(time() - temp)
        measure_ram()

        step_ram_function = lambda sim: measure_ram()

        #% FIRST RUN: SIMULATION NEEDED TO NORMALIZE

        temp = time()
        sim.run(mp.at_beginning(step_ram_function),
                mp.at_time(int(until_after_sources / 2), step_ram_function),
                mp.at_end(step_ram_function),
                until_after_sources=until_after_sources)
        #     mp.stop_when_fields_decayed(
        # np.mean(wlen_range), # dT = mean period of source
        # mp.Ez, # Component of field to check
        # mp.Vector3(0.5*cell_width - pml_width, 0, 0), # Where to check
        # 1e-3)) # Factor to decay
        enlapsed.append(time() - temp)

        #% SAVE MID DATA

        for p in params_list:
            params[p] = eval(p)

        flux_path = vm.save_midflux(sim, box_x1, box_x2, box_y1, box_y2,
                                    box_z1, box_z2, near2far_box, params, path)

        freqs = np.asarray(mp.get_flux_freqs(box_x1))
        box_x1_flux0 = np.asarray(mp.get_fluxes(box_x1))
        box_x2_flux0 = np.asarray(mp.get_fluxes(box_x2))
        box_y1_flux0 = np.asarray(mp.get_fluxes(box_y1))
        box_y2_flux0 = np.asarray(mp.get_fluxes(box_y2))
        box_z1_flux0 = np.asarray(mp.get_fluxes(box_z1))
        box_z2_flux0 = np.asarray(mp.get_fluxes(box_z2))

        data_mid = np.array([
            1e3 * from_um_factor / freqs, box_x1_flux0, box_x2_flux0,
            box_y1_flux0, box_y2_flux0, box_z1_flux0, box_z2_flux0
        ]).T

        header_mid = [
            "Longitud de onda [nm]", "Flujo X10 [u.a.]", "Flujo X20 [u.a]",
            "Flujo Y10 [u.a]", "Flujo Y20 [u.a]", "Flujo Z10 [u.a]",
            "Flujo Z20 [u.a]"
        ]

        if vm.parallel_assign(0, n_processes, parallel):
            vs.savetxt(file("MidFlux.txt"),
                       data_mid,
                       header=header_mid,
                       footer=params)

        if not split_chunks_evenly:
            vm.save_chunks(sim, params, path)

        if parallel:
            f = h5.File(file("MidRAM.h5"),
                        "w",
                        driver='mpio',
                        comm=MPI.COMM_WORLD)
            current_process = mp.my_rank()
            f.create_dataset("RAM", (len(used_ram), np_process), dtype="float")
            f["RAM"][:, current_process] = used_ram
            for a in params:
                f["RAM"].attrs[a] = params[a]
            f.create_dataset("SWAP", (len(used_ram), np_process), dtype="int")
            f["SWAP"][:, current_process] = swapped_ram
            for a in params:
                f["SWAP"].attrs[a] = params[a]
        else:
            f = h5.File(file("MidRAM.h5"), "w")
            f.create_dataset("RAM", data=used_ram)
            for a in params:
                f["RAM"].attrs[a] = params[a]
            f.create_dataset("SWAP", data=swapped_ram)
            for a in params:
                f["SWAP"].attrs[a] = params[a]
        f.close()
        del f

        #% PLOT FLUX FOURIER MID DATA

        if vm.parallel_assign(1, np_process, parallel):
            ylims = (np.min(data_mid[:, 1:]), np.max(data_mid[:, 1:]))
            ylims = (ylims[0] - .1 * (ylims[1] - ylims[0]),
                     ylims[1] + .1 * (ylims[1] - ylims[0]))

            fig, ax = plt.subplots(3, 2, sharex=True)
            fig.subplots_adjust(hspace=0, wspace=.05)
            for a in ax[:, 1]:
                a.yaxis.tick_right()
                a.yaxis.set_label_position("right")
            for a, h in zip(np.reshape(ax, 6), header_mid[1:]):
                a.set_ylabel(h)

            for d, a in zip(data_mid[:, 1:].T, np.reshape(ax, 6)):
                a.plot(1e3 * from_um_factor / freqs, d)
                a.set_ylim(*ylims)
            ax[-1, 0].set_xlabel("Wavelength [nm]")
            ax[-1, 1].set_xlabel("Wavelength [nm]")

            plt.savefig(file("MidFlux.png"))
            del fig, ax, ylims, a, h

        sim.reset_meep()

    #%% SECOND RUN: SETUP

    measure_ram()

    sim = mp.Simulation(
        resolution=resolution,
        cell_size=cell_size,
        boundary_layers=pml_layers,
        sources=sources,
        k_point=mp.Vector3(),
        Courant=courant,
        default_material=mp.Medium(index=submerged_index),
        output_single_precision=True,
        split_chunks_evenly=split_chunks_evenly,
        chunk_layout=chunk_layout,
        # symmetries=symmetries,
        geometry=geometry)

    measure_ram()

    box_x1 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(x=-flux_box_size / 2),
                      size=mp.Vector3(0, flux_box_size, flux_box_size)))
    box_x2 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(x=+flux_box_size / 2),
                      size=mp.Vector3(0, flux_box_size, flux_box_size)))
    box_y1 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(y=-flux_box_size / 2),
                      size=mp.Vector3(flux_box_size, 0, flux_box_size)))
    box_y2 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(y=+flux_box_size / 2),
                      size=mp.Vector3(flux_box_size, 0, flux_box_size)))
    box_z1 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(z=-flux_box_size / 2),
                      size=mp.Vector3(flux_box_size, flux_box_size, 0)))
    box_z2 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(z=+flux_box_size / 2),
                      size=mp.Vector3(flux_box_size, flux_box_size, 0)))

    measure_ram()

    if near2far:
        near2far_box = sim.add_near2far(
            freq_center, freq_width, nfreq,
            mp.Near2FarRegion(center=mp.Vector3(x=-flux_box_size / 2),
                              size=mp.Vector3(0, flux_box_size, flux_box_size),
                              weight=-1),
            mp.Near2FarRegion(center=mp.Vector3(x=+flux_box_size / 2),
                              size=mp.Vector3(0, flux_box_size, flux_box_size),
                              weight=+1),
            mp.Near2FarRegion(center=mp.Vector3(y=-flux_box_size / 2),
                              size=mp.Vector3(flux_box_size, 0, flux_box_size),
                              weight=-1),
            mp.Near2FarRegion(center=mp.Vector3(y=+flux_box_size / 2),
                              size=mp.Vector3(flux_box_size, 0, flux_box_size),
                              weight=+1),
            mp.Near2FarRegion(center=mp.Vector3(z=-flux_box_size / 2),
                              size=mp.Vector3(flux_box_size, flux_box_size, 0),
                              weight=-1),
            mp.Near2FarRegion(center=mp.Vector3(z=+flux_box_size / 2),
                              size=mp.Vector3(flux_box_size, flux_box_size, 0),
                              weight=+1))
        measure_ram()
    else:
        near2far_box = None
        # used_ram.append(used_ram[-1])

    #%% SECOND RUN: INITIALIZE

    temp = time()
    sim.init_sim()
    enlapsed.append(time() - temp)
    measure_ram()

    #%% LOAD FLUX FROM FILE

    vm.load_midflux(sim, box_x1, box_x2, box_y1, box_y2, box_z1, box_z2,
                    near2far_box, flux_path)

    measure_ram()

    freqs = np.asarray(mp.get_flux_freqs(box_x1))
    box_x1_flux0 = np.asarray(mp.get_fluxes(box_x1))
    box_x1_data = sim.get_flux_data(box_x1)
    box_x2_data = sim.get_flux_data(box_x2)
    box_y1_data = sim.get_flux_data(box_y1)
    box_y2_data = sim.get_flux_data(box_y2)
    box_z1_data = sim.get_flux_data(box_z1)
    box_z2_data = sim.get_flux_data(box_z2)
    if near2far: near2far_data = sim.get_near2far_data(near2far_box)

    temp = time()
    sim.load_minus_flux_data(box_x1, box_x1_data)
    sim.load_minus_flux_data(box_x2, box_x2_data)
    sim.load_minus_flux_data(box_y1, box_y1_data)
    sim.load_minus_flux_data(box_y2, box_y2_data)
    sim.load_minus_flux_data(box_z1, box_z1_data)
    sim.load_minus_flux_data(box_z2, box_z2_data)
    if near2far: sim.load_minus_near2far_data(near2far_box, near2far_data)
    enlapsed.append(time() - temp)
    del box_x1_data, box_x2_data, box_y1_data, box_y2_data
    del box_z1_data, box_z2_data
    if near2far: del near2far_data

    measure_ram()

    #%% SECOND RUN: SIMULATION :D

    step_ram_function = lambda sim: measure_ram()

    temp = time()
    sim.run(mp.at_beginning(step_ram_function),
            mp.at_time(int(second_time_factor * until_after_sources / 2),
                       step_ram_function),
            mp.at_end(step_ram_function),
            until_after_sources=second_time_factor * until_after_sources)
    #     mp.stop_when_fields_decayed(
    # np.mean(wlen_range), # dT = mean period of source
    # mp.Ez, # Component of field to check
    # mp.Vector3(0.5*cell_width - pml_width, 0, 0), # Where to check
    # 1e-3)) # Factor to decay
    enlapsed.append(time() - temp)
    del temp
    # Aprox 30 periods of lowest frequency, using T=λ/c=λ in Meep units

    box_x1_flux = np.asarray(mp.get_fluxes(box_x1))
    box_x2_flux = np.asarray(mp.get_fluxes(box_x2))
    box_y1_flux = np.asarray(mp.get_fluxes(box_y1))
    box_y2_flux = np.asarray(mp.get_fluxes(box_y2))
    box_z1_flux = np.asarray(mp.get_fluxes(box_z1))
    box_z2_flux = np.asarray(mp.get_fluxes(box_z2))

    #%% SCATTERING ANALYSIS

    scatt_flux = box_x1_flux - box_x2_flux
    scatt_flux = scatt_flux + box_y1_flux - box_y2_flux
    scatt_flux = scatt_flux + box_z1_flux - box_z2_flux

    intensity = box_x1_flux0 / (flux_box_size)**2
    # Flux of one of the six monitor planes / Área
    # (the closest one, facing the planewave source)
    # This is why the six sides of the flux box are separated
    # (Otherwise, the box could've been one flux object with weights ±1 per side)

    scatt_cross_section = np.divide(scatt_flux, intensity)
    # Scattering cross section σ =
    # = scattered power in all directions / incident intensity.

    scatt_eff_meep = -1 * scatt_cross_section / (np.pi * r**2)
    # Scattering efficiency =
    # = scattering cross section / cross sectional area of the sphere

    freqs = np.array(freqs)
    scatt_eff_theory = [
        ps.MieQ(np.sqrt(medium.epsilon(f)[0, 0] * medium.mu(f)[0, 0]),
                1e3 * from_um_factor / f,
                2 * r * 1e3 * from_um_factor,
                nMedium=submerged_index,
                asDict=True)['Qsca'] for f in freqs
    ]
    # The simulation results are validated by comparing with
    # analytic theory of PyMieScatt module

    #%% ANGULAR PATTERN ANALYSIS

    if near2far:

        fraunhofer_distance = 8 * (r**2) / min(wlen_range)
        radial_distance = max(10 * fraunhofer_distance, 1.5 * cell_width / 2)
        # radius of far-field circle must be at least Fraunhofer distance

        azimuthal_angle = np.arange(0, 2 + 2 / nazimuthal,
                                    2 / nazimuthal)  # in multiples of pi
        polar_angle = np.arange(0, 1 + 1 / npolar, 1 / npolar)

        poynting_x = []
        poynting_y = []
        poynting_z = []
        poynting_r = []

        for phi in azimuthal_angle:

            poynting_x.append([])
            poynting_y.append([])
            poynting_z.append([])
            poynting_r.append([])

            for theta in polar_angle:

                farfield_dict = sim.get_farfields(
                    near2far_box,
                    1,
                    where=mp.Volume(center=mp.Vector3(
                        radial_distance * np.cos(np.pi * phi) *
                        np.sin(np.pi * theta),
                        radial_distance * np.sin(np.pi * phi) *
                        np.sin(np.pi *
                               theta), radial_distance *
                        np.cos(np.pi * theta))))

                Px = farfield_dict["Ey"] * np.conjugate(farfield_dict["Hz"])
                Px -= farfield_dict["Ez"] * np.conjugate(farfield_dict["Hy"])
                Py = farfield_dict["Ez"] * np.conjugate(farfield_dict["Hx"])
                Py -= farfield_dict["Ex"] * np.conjugate(farfield_dict["Hz"])
                Pz = farfield_dict["Ex"] * np.conjugate(farfield_dict["Hy"])
                Pz -= farfield_dict["Ey"] * np.conjugate(farfield_dict["Hx"])

                Px = np.real(Px)
                Py = np.real(Py)
                Pz = np.real(Pz)

                poynting_x[-1].append(Px)
                poynting_y[-1].append(Py)
                poynting_z[-1].append(Pz)
                poynting_r[-1].append(
                    np.sqrt(np.square(Px) + np.square(Py) + np.square(Pz)))

        poynting_x = np.array(poynting_x)
        poynting_y = np.array(poynting_y)
        poynting_z = np.array(poynting_z)
        poynting_r = np.array(poynting_r)

    #%% SAVE FINAL DATA

    for p in params_list:
        params[p] = eval(p)

    data = np.array(
        [1e3 * from_um_factor / freqs, scatt_eff_meep, scatt_eff_theory]).T

    header = [
        "Longitud de onda [nm]", "Sección eficaz efectiva (Meep) [u.a.]",
        "Sección eficaz efectiva (Theory) [u.a.]"
    ]

    data_base = np.array([
        1e3 * from_um_factor / freqs, box_x1_flux0, box_x1_flux, box_x2_flux,
        box_y1_flux, box_y2_flux, box_z1_flux, box_z2_flux, intensity,
        scatt_flux, scatt_cross_section
    ]).T

    header_base = [
        "Longitud de onda [nm]", "Flujo X10 [u.a.]", "Flujo X1 [u.a]",
        "Flujo X2 [u.a]", "Flujo Y1 [u.a]", "Flujo Y2 [u.a]", "Flujo Z1 [u.a]",
        "Flujo Z2 [u.a]", "Intensidad incidente [u.a.]",
        "Flujo scattereado [u.a.]", "Sección eficaz de scattering [u.a.]"
    ]

    if vm.parallel_assign(0, np_process, parallel):
        vs.savetxt(file("Results.txt"), data, header=header, footer=params)
        vs.savetxt(file("BaseResults.txt"),
                   data_base,
                   header=header_base,
                   footer=params)

    if near2far:

        header_near2far = [
            "Poynting medio Px [u.a.]", "Poynting medio Py [u.a.]",
            "Poynting medio Pz [u.a.]", "Poynting medio Pr [u.a.]"
        ]

        data_near2far = [
            poynting_x.reshape(poynting_x.size),
            poynting_y.reshape(poynting_y.size),
            poynting_z.reshape(poynting_z.size),
            poynting_r.reshape(poynting_r.size)
        ]

        if vm.parallel_assign(1, np_process, parallel):
            vs.savetxt(file("Near2FarResults.txt"),
                       data_near2far,
                       header=header_near2far,
                       footer=params)

    if not split_chunks_evenly:
        vm.save_chunks(sim, params, path)

    if parallel:
        f = h5.File(file("RAM.h5"), "w", driver='mpio', comm=MPI.COMM_WORLD)
        current_process = mp.my_rank()
        f.create_dataset("RAM", (len(used_ram), np_process), dtype="float")
        f["RAM"][:, current_process] = used_ram
        for a in params:
            f["RAM"].attrs[a] = params[a]
        f.create_dataset("SWAP", (len(used_ram), np_process), dtype="int")
        f["SWAP"][:, current_process] = swapped_ram
        for a in params:
            f["SWAP"].attrs[a] = params[a]
    else:
        f = h5.File(file("RAM.h5"), "w")
        f.create_dataset("RAM", data=used_ram)
        for a in params:
            f["RAM"].attrs[a] = params[a]
        f.create_dataset("SWAP", data=swapped_ram)
        for a in params:
            f["SWAP"].attrs[a] = params[a]
    f.close()
    del f

    if flux_needed and vm.parallel_assign(0, np_process, parallel):
        os.remove(file("MidRAM.h5"))

    #%% PLOT ALL TOGETHER

    if vm.parallel_assign(0, np_process, parallel) and surface_index == 1:
        plt.figure()
        plt.plot(1e3 * from_um_factor / freqs,
                 scatt_eff_meep,
                 'bo-',
                 label='Meep')
        plt.plot(1e3 * from_um_factor / freqs,
                 scatt_eff_theory,
                 'ro-',
                 label='Theory')
        plt.xlabel('Wavelength [nm]')
        plt.ylabel('Scattering efficiency [σ/πr$^{2}$]')
        plt.legend()
        plt.title('Scattering of Au Sphere With {:.1f} nm Radius'.format(
            r * from_um_factor * 1e3))
        plt.tight_layout()
        plt.savefig(file("Comparison.png"))

    #%% PLOT SEPARATE

    if vm.parallel_assign(1, np_process, parallel):
        plt.figure()
        plt.plot(1e3 * from_um_factor / freqs,
                 scatt_eff_meep,
                 'bo-',
                 label='Meep')
        plt.xlabel('Wavelength [nm]')
        plt.ylabel('Scattering efficiency [σ/πr$^{2}$]')
        plt.legend()
        plt.title('Scattering of Au Sphere With {:.1f} nm Radius'.format(
            r * from_um_factor * 1e3))
        plt.tight_layout()
        plt.savefig(file("Meep.png"))

    if vm.parallel_assign(0, np_process, parallel) and surface_index == 1:
        plt.figure()
        plt.plot(1e3 * from_um_factor / freqs,
                 scatt_eff_theory,
                 'ro-',
                 label='Theory')
        plt.xlabel('Wavelength [nm]')
        plt.ylabel('Scattering efficiency [σ/πr$^{2}$]')
        plt.legend()
        plt.title('Scattering of Au Sphere With {:.1f} nm Radius'.format(r *
                                                                         10))
        plt.tight_layout()
        plt.savefig(file("Theory.png"))

    #%% PLOT ONE ABOVE THE OTHER

    if vm.parallel_assign(1, np_process, parallel) and surface_index == 1:
        fig, axes = plt.subplots(nrows=2, sharex=True)
        fig.subplots_adjust(hspace=0)
        plt.suptitle('Scattering of Au Sphere With {:.1f} nm Radius'.format(
            r * from_um_factor * 1e3))

        axes[0].plot(1e3 * from_um_factor / freqs,
                     scatt_eff_meep,
                     'bo-',
                     label='Meep')
        axes[0].yaxis.tick_right()
        axes[0].set_ylabel('Scattering efficiency [σ/πr$^{2}$]')
        axes[0].legend()

        axes[1].plot(1e3 * from_um_factor / freqs,
                     scatt_eff_theory,
                     'ro-',
                     label='Theory')
        axes[1].set_xlabel('Wavelength [nm]')
        axes[1].set_ylabel('Scattering efficiency [σ/πr$^{2}$]')
        axes[1].legend()

        plt.savefig(file("SeparatedComparison.png"))

    #%% PLOT FLUX FOURIER FINAL DATA

    if vm.parallel_assign(0, np_process, parallel):

        ylims = (np.min(data_base[:, 2:8]), np.max(data_base[:, 2:8]))
        ylims = (ylims[0] - .1 * (ylims[1] - ylims[0]),
                 ylims[1] + .1 * (ylims[1] - ylims[0]))

        fig, ax = plt.subplots(3, 2, sharex=True)
        fig.subplots_adjust(hspace=0, wspace=.05)
        plt.suptitle('Final flux of Au Sphere With {:.1f} nm Radius'.format(
            r * from_um_factor * 1e3))
        for a in ax[:, 1]:
            a.yaxis.tick_right()
            a.yaxis.set_label_position("right")
        for a, h in zip(np.reshape(ax, 6), header_base[1:7]):
            a.set_ylabel(h)

        for d, a in zip(data_base[:, 3:9].T, np.reshape(ax, 6)):
            a.plot(1e3 * from_um_factor / freqs, d)
            a.set_ylim(*ylims)
        ax[-1, 0].set_xlabel("Wavelength [nm]")
        ax[-1, 1].set_xlabel("Wavelength [nm]")

        plt.savefig(file("FinalFlux.png"))

    #%% PLOT ANGULAR PATTERN IN 3D

    if near2far and vm.parallel_assign(1, np_process, parallel):

        freq_index = np.argmin(np.abs(freqs - freq_center))

        fig = plt.figure()
        plt.suptitle(
            'Angular Pattern of Au Sphere With {:.1f} nm Radius at {:.1f} nm'.
            format(r * from_um_factor * 1e3,
                   from_um_factor * 1e3 / freqs[freq_index]))
        ax = fig.add_subplot(1, 1, 1, projection='3d')
        ax.plot_surface(poynting_x[:, :, freq_index],
                        poynting_y[:, :, freq_index],
                        poynting_z[:, :, freq_index],
                        cmap=plt.get_cmap('jet'),
                        linewidth=1,
                        antialiased=False,
                        alpha=0.5)
        ax.set_xlabel(r"$P_x$")
        ax.set_ylabel(r"$P_y$")
        ax.set_zlabel(r"$P_z$")

        plt.savefig(file("AngularPattern.png"))

    #%% PLOT ANGULAR PATTERN PROFILE FOR DIFFERENT POLAR ANGLES

    if near2far and vm.parallel_assign(0, np_process, parallel):

        freq_index = np.argmin(np.abs(freqs - freq_center))
        index = [
            list(polar_angle).index(alpha) for alpha in [0, .25, .5, .75, 1]
        ]

        plt.figure()
        plt.suptitle(
            'Angular Pattern of Au Sphere With {:.1f} nm Radius at {:.1f} nm'.
            format(r * from_um_factor * 1e3,
                   from_um_factor * 1e3 / freqs[freq_index]))
        ax_plain = plt.axes()
        for i in index:
            ax_plain.plot(poynting_x[:, i, freq_index],
                          poynting_y[:, i, freq_index],
                          ".-",
                          label=rf"$\theta$ = {polar_angle[i]:.2f} $\pi$")
        plt.legend()
        ax_plain.set_xlabel(r"$P_x$")
        ax_plain.set_ylabel(r"$P_y$")
        ax_plain.set_aspect("equal")

        plt.savefig(file("AngularPolar.png"))

    #%% PLOT ANGULAR PATTERN PROFILE FOR DIFFERENT AZIMUTHAL ANGLES

    if near2far and vm.parallel_assign(1, np_process, parallel):

        freq_index = np.argmin(np.abs(freqs - freq_center))
        index = [
            list(azimuthal_angle).index(alpha)
            for alpha in [0, .25, .5, .75, 1, 1.25, 1.5, 1.75, 2]
        ]

        plt.figure()
        plt.suptitle(
            'Angular Pattern of Au Sphere With {:.1f} nm Radius at {:.1f} nm'.
            format(r * from_um_factor * 1e3,
                   from_um_factor * 1e3 / freqs[freq_index]))
        ax_plain = plt.axes()
        for i in index:
            ax_plain.plot(poynting_x[i, :, freq_index],
                          poynting_z[i, :, freq_index],
                          ".-",
                          label=rf"$\phi$ = {azimuthal_angle[i]:.2f} $\pi$")
        plt.legend()
        ax_plain.set_xlabel(r"$P_x$")
        ax_plain.set_ylabel(r"$P_z$")

        plt.savefig(file("AngularAzimuthal.png"))

    if near2far and vm.parallel_assign(0, np_process, parallel):

        freq_index = np.argmin(np.abs(freqs - freq_center))
        index = [
            list(azimuthal_angle).index(alpha)
            for alpha in [0, .25, .5, .75, 1, 1.25, 1.5, 1.75, 2]
        ]

        plt.figure()
        plt.suptitle(
            'Angular Pattern of Au Sphere With {:.1f} nm Radius at {:.1f} nm'.
            format(r * from_um_factor * 1e3,
                   from_um_factor * 1e3 / freqs[freq_index]))
        ax_plain = plt.axes()
        for i in index:
            ax_plain.plot(np.sqrt(
                np.square(poynting_x[i, :, freq_index]) +
                np.square(poynting_y[i, :, freq_index])),
                          poynting_z[i, :, freq_index],
                          ".-",
                          label=rf"$\phi$ = {azimuthal_angle[i]:.2f} $\pi$")
        plt.legend()
        ax_plain.set_xlabel(r"$P_\rho$")
        ax_plain.set_ylabel(r"$P_z$")

        plt.savefig(file("AngularAzimuthalAbs.png"))
def main(series, folder, resolution, from_um_factor, d, h, paper, wlen_range,
         nfreq):

    #%% PARAMETERS

    ### MEAN PARAMETERS

    # Units: 10 nm as length unit
    from_um_factor = 10e-3  # Conversion of 1 μm to my length unit (=10nm/1μm)

    # Au sphere
    medium = import_medium("Au", from_um_factor,
                           paper=paper)  # Medium of sphere: gold (Au)
    d = d / (from_um_factor * 1e3)  # Diameter is now in Meep units
    h = h / (from_um_factor * 1e3)  # Height is now in Meep units

    # Frequency and wavelength
    wlen_range = wlen_range / (from_um_factor * 1e3
                               )  # Wavelength range now in Meep units
    nfreq = 100  # Number of frequencies to discretize range
    cutoff = 3.2

    # Computation time
    enlapsed = []
    time_factor_cell = 1.2
    until_after_sources = False
    second_time_factor = 10

    # Saving directories
    home = vs.get_home()

    ### OTHER PARAMETERS

    # Units
    # uman = MeepUnitsManager(from_um_factor=from_um_factor)

    # Frequency and wavelength
    freq_range = 1 / wlen_range  # Hz range in Meep units from highest to lowest
    freq_center = np.mean(freq_range)
    freq_width = max(freq_range) - min(freq_range)

    # Space configuration
    pml_width = 0.38 * max(wlen_range)
    air_width = max(d / 2, h / 2) / 2  # 0.5 * max(wlen_range)

    #%% GENERAL GEOMETRY SETUP

    air_width = air_width - air_width % (1 / resolution)

    pml_width = pml_width - pml_width % (1 / resolution)
    pml_layers = [mp.PML(thickness=pml_width)]

    # symmetries = [mp.Mirror(mp.Y),
    #               mp.Mirror(mp.Z, phase=-1)]
    # Two mirror planes reduce cell size to 1/4
    # Issue related that lead me to comment this lines:
    # https://github.com/NanoComp/meep/issues/1484

    cell_width_z = 2 * (pml_width + air_width + h / 2
                        )  # Parallel to polarization.
    cell_width_z = cell_width_z - cell_width_z % (1 / resolution)

    cell_width_r = 2 * (pml_width + air_width + d / 2
                        )  # Parallel to incidence.
    cell_width_r = cell_width_r - cell_width_r % (1 / resolution)

    cell_size = mp.Vector3(cell_width_r, cell_width_r, cell_width_z)

    source_center = -0.5 * cell_width_r + pml_width
    # print("Resto Source Center: {}".format(source_center%(1/resolution)))
    sources = [
        mp.Source(mp.GaussianSource(freq_center,
                                    fwidth=freq_width,
                                    is_integrated=True,
                                    cutoff=cutoff),
                  center=mp.Vector3(source_center),
                  size=mp.Vector3(0, cell_width_r, cell_width_z),
                  component=mp.Ez)
    ]
    # Ez-polarized planewave pulse
    # (its size parameter fills the entire cell in 2d)
    # >> The planewave source extends into the PML
    # ==> is_integrated=True must be specified

    if time_factor_cell is not False:
        until_after_sources = time_factor_cell * cell_width_r
    else:
        if until_after_sources is False:
            raise ValueError(
                "Either time_factor_cell or until_after_sources must be specified"
            )
        time_factor_cell = until_after_sources / cell_width_r
    # Enough time for the pulse to pass through all the cell
    # Originally: Aprox 3 periods of lowest frequency, using T=λ/c=λ in Meep units
    # Now: Aprox 3 periods of highest frequency, using T=λ/c=λ in Meep units

    geometry = [
        mp.Ellipsoid(size=mp.Vector3(d, d, h),
                     material=medium,
                     center=mp.Vector3())
    ]
    # Au ellipsoid with frequency-dependant characteristics imported from Meep.

    path = os.path.join(home, folder, f"{series}")
    if not os.path.isdir(path) and mp.am_master():
        vs.new_dir(path)
    file = lambda f: os.path.join(path, f)

    #%% FIRST RUN: SET UP

    sim = mp.Simulation(resolution=resolution,
                        cell_size=cell_size,
                        boundary_layers=pml_layers,
                        sources=sources,
                        k_point=mp.Vector3())  #,
    # symmetries=symmetries)
    # >> k_point zero specifies boundary conditions needed
    # for the source to be infinitely extended

    # Scattered power --> Computed by surrounding it with closed DFT flux box
    # (its size and orientation are irrelevant because of Poynting's theorem)
    box_x1 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(x=-d / 2), size=mp.Vector3(0, d, h)))
    box_x2 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(x=+d / 2), size=mp.Vector3(0, d, h)))
    box_y1 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(y=-d / 2), size=mp.Vector3(d, 0, h)))
    box_y2 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(y=+d / 2), size=mp.Vector3(d, 0, h)))
    box_z1 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(z=-h / 2), size=mp.Vector3(d, d, 0)))
    box_z2 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(z=+h / 2), size=mp.Vector3(d, d, 0)))
    # Funny you can encase the ellipsoid (diameter d and height h) so closely
    # (dxdxh-sided box)

    #%% FIRST RUN: INITIALIZE

    temp = time()
    sim.init_sim()
    enlapsed.append(time() - temp)
    """
    112x112x112 with resolution 4
    (48 cells inside diameter)
    ==> 30 s to build
    
    112x112x112 with resolution 2
    (24 cells inside diameter)
    ==> 4.26 s to build
    
    116x116x116 with resolution 2
    (24 cells inside diameter)
    ==> 5.63 s
    
    98 x 98 x 98 with resolution 3
    (36 cells inside diameter)
    ==> 9.57 s
    
    172x172x172 with resolution 2
    (24 cells inside diameter)
    ==> 17.47 s
    
    67 x 67 x 67 with resolution 4
    (48 cells inside diameter)
    ==> 7.06 s
    
    67,375 x 67,375 x 67,375 with resolution 8
    (100 cells inside diameter)
    ==> 56.14 s
    """

    #%% FIRST RUN: SIMULATION NEEDED TO NORMALIZE

    temp = time()
    sim.run(until_after_sources=until_after_sources)
    #     mp.stop_when_fields_decayed(
    # np.mean(wlen_range), # dT = mean period of source
    # mp.Ez, # Component of field to check
    # mp.Vector3(0.5*cell_width - pml_width, 0, 0), # Where to check
    # 1e-3)) # Factor to decay
    enlapsed.append(time() - temp)
    """
    112x112x112 with resolution 2
    (24 cells inside diameter)
    ==> 135.95 s to complete 1st run
    
    116x116x116 with resolution 2
    (24 cells inside diameter)
    ==> 208 s to complete 1st run
    
    67 x 67 x 67 with resolution 4
    (48 cells inside diameter)
    ==> 2000 s = 33 min to complete 1st run
    """

    freqs = np.asarray(mp.get_flux_freqs(box_x1))
    box_x1_data = sim.get_flux_data(box_x1)
    box_x2_data = sim.get_flux_data(box_x2)
    box_y1_data = sim.get_flux_data(box_y1)
    box_y2_data = sim.get_flux_data(box_y2)
    box_z1_data = sim.get_flux_data(box_z1)
    box_z2_data = sim.get_flux_data(box_z2)

    box_x1_flux0 = np.asarray(mp.get_fluxes(box_x1))
    box_x2_flux0 = np.asarray(mp.get_fluxes(box_x2))
    box_y1_flux0 = np.asarray(mp.get_fluxes(box_y1))
    box_y2_flux0 = np.asarray(mp.get_fluxes(box_y2))
    box_z1_flux0 = np.asarray(mp.get_fluxes(box_z1))
    box_z2_flux0 = np.asarray(mp.get_fluxes(box_z2))

    # field = sim.get_array(center=mp.Vector3(),
    #                       size=(cell_width, cell_width, cell_width),
    #                       component=mp.Ez)

    sim.reset_meep()

    #%% SAVE MID DATA

    params = dict(
        from_um_factor=from_um_factor,
        resolution=resolution,
        d=d,
        h=h,
        paper=paper,
        wlen_range=wlen_range,
        nfreq=nfreq,
        cutoff=cutoff,
        pml_width=pml_width,
        air_width=air_width,
        source_center=source_center,
        enlapsed=enlapsed,
        series=series,
        folder=folder,
        pc=pc,
        until_after_sources=until_after_sources,
        time_factor_cell=time_factor_cell,
        second_time_factor=second_time_factor,
    )

    # f = h5.File(file("MidField.h5"), "w")
    # f.create_dataset("Ez", data=field)
    # for a in params: f["Ez"].attrs[a] = params[a]
    # f.close()
    # del f

    data_mid = np.array([
        1e3 * from_um_factor / freqs, box_x1_flux0, box_x2_flux0, box_y1_flux0,
        box_y2_flux0, box_z1_flux0, box_z2_flux0
    ]).T

    header_mid = [
        "Longitud de onda [nm]", "Flujo X10 [u.a.]", "Flujo X20 [u.a]",
        "Flujo Y10 [u.a]", "Flujo Y20 [u.a]", "Flujo Z10 [u.a]",
        "Flujo Z20 [u.a]"
    ]

    if mp.am_master():
        vs.savetxt(file("MidFlux.txt"),
                   data_mid,
                   header=header_mid,
                   footer=params)

    #%% PLOT FLUX FOURIER MID DATA

    if mp.my_rank() == 1:
        ylims = (np.min(data_mid[:, 1:]), np.max(data_mid[:, 1:]))
        ylims = (ylims[0] - .1 * (ylims[1] - ylims[0]),
                 ylims[1] + .1 * (ylims[1] - ylims[0]))

        fig, ax = plt.subplots(3, 2, sharex=True)
        fig.subplots_adjust(hspace=0, wspace=.05)
        for a in ax[:, 1]:
            a.yaxis.tick_right()
            a.yaxis.set_label_position("right")
        for a, hm in zip(np.reshape(ax, 6), header_mid[1:]):
            a.set_ylabel(hm)

        for dm, a in zip(data_mid[:, 1:].T, np.reshape(ax, 6)):
            a.plot(1e3 * from_um_factor / freqs, dm)
            a.set_ylim(*ylims)
        ax[-1, 0].set_xlabel("Wavelength [nm]")
        ax[-1, 1].set_xlabel("Wavelength [nm]")

        plt.savefig(file("MidFlux.png"))

    #%% PLOT FLUX WALLS FIELD

    # if mp.am_master():
    # index_to_space = lambda i : i/resolution - cell_width/2
    # space_to_index = lambda x : round(resolution * (x + cell_width/2))

    # field_walls = [field[space_to_index(-r),:,:],
    #                 field[space_to_index(r),:,:],
    #                 field[:,space_to_index(-r),:],
    #                 field[:,space_to_index(r),:],
    #                 field[:,:,space_to_index(-r)],
    #                 field[:,:,space_to_index(r)]]

    # zlims = (np.min([np.min(f) for f in field_walls]),
    #           np.max([np.max(f) for f in field_walls]))

    # fig, ax = plt.subplots(3, 2)
    # fig.subplots_adjust(hspace=0.25)
    # for a, hm in zip(np.reshape(ax, 6), header_mid[1:]):
    #     a.set_title(hm.split(" ")[1].split("0")[0])

    # for f, a in zip(field_walls, np.reshape(ax, 6)):
    #     a.imshow(f.T, interpolation='spline36', cmap='RdBu',
    #               vmin=zlims[0], vmax=zlims[1])
    #     a.axis("off")

    # plt.savefig(file("MidField.png"))

    #%% SECOND RUN: SETUP

    sim = mp.Simulation(
        resolution=resolution,
        cell_size=cell_size,
        boundary_layers=pml_layers,
        sources=sources,
        k_point=mp.Vector3(),
        # symmetries=symmetries,
        geometry=geometry)

    box_x1 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(x=-d / 2), size=mp.Vector3(0, d, h)))
    box_x2 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(x=+d / 2), size=mp.Vector3(0, d, h)))
    box_y1 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(y=-d / 2), size=mp.Vector3(d, 0, h)))
    box_y2 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(y=+d / 2), size=mp.Vector3(d, 0, h)))
    box_z1 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(z=-h / 2), size=mp.Vector3(d, d, 0)))
    box_z2 = sim.add_flux(
        freq_center, freq_width, nfreq,
        mp.FluxRegion(center=mp.Vector3(z=+h / 2), size=mp.Vector3(d, d, 0)))

    #%% SECOND RUN: INITIALIZE

    temp = time()
    sim.init_sim()
    enlapsed.append(time() - temp)
    """
    112x112x112 with resolution 2
    (24 cells in diameter)
    ==> 5.16 s to build with sphere
    
    116x116x116 with resolution 2
    (24 cells inside diameter)
    ==> 9.71 s to build with sphere
    
    67 x 67 x 67 with resolution 4
    (48 cells inside diameter)
    ==> 14.47 s to build with sphere
    """

    temp = time()
    sim.load_minus_flux_data(box_x1, box_x1_data)
    sim.load_minus_flux_data(box_x2, box_x2_data)
    sim.load_minus_flux_data(box_y1, box_y1_data)
    sim.load_minus_flux_data(box_y2, box_y2_data)
    sim.load_minus_flux_data(box_z1, box_z1_data)
    sim.load_minus_flux_data(box_z2, box_z2_data)
    enlapsed.append(time() - temp)
    del box_x1_data, box_x2_data, box_y1_data, box_y2_data
    del box_z1_data, box_z2_data
    """
    112x112x112 with resolution 2
    (24 cells in diameter)
    ==> 0.016 s to add flux
    
    116x116x116 with resolution 2
    (24 cells inside diameter)
    ==> 0.021 s to add flux
    
    67 x 67 x 67 with resolution 4
    (48 cells inside diameter)
    ==> 0.043 s to add flux
    """

    #%% SECOND RUN: SIMULATION :D

    temp = time()
    sim.run(until_after_sources=second_time_factor * until_after_sources)
    #     mp.stop_when_fields_decayed(
    # np.mean(wlen_range), # dT = mean period of source
    # mp.Ez, # Component of field to check
    # mp.Vector3(0.5*cell_width - pml_width, 0, 0), # Where to check
    # 1e-3)) # Factor to decay
    enlapsed.append(time() - temp)
    del temp
    # Aprox 30 periods of lowest frequency, using T=λ/c=λ in Meep units

    box_x1_flux = np.asarray(mp.get_fluxes(box_x1))
    box_x2_flux = np.asarray(mp.get_fluxes(box_x2))
    box_y1_flux = np.asarray(mp.get_fluxes(box_y1))
    box_y2_flux = np.asarray(mp.get_fluxes(box_y2))
    box_z1_flux = np.asarray(mp.get_fluxes(box_z1))
    box_z2_flux = np.asarray(mp.get_fluxes(box_z2))

    #%% ANALYSIS

    scatt_flux = box_x1_flux - box_x2_flux
    scatt_flux = scatt_flux + box_y1_flux - box_y2_flux
    scatt_flux = scatt_flux + box_z1_flux - box_z2_flux

    intensity = box_x1_flux0 / (d**2)
    # Flux of one of the six monitor planes / Área
    # (the closest one, facing the planewave source)
    # This is why the six sides of the flux box are separated
    # (Otherwise, the box could've been one flux object with weights ±1 per side)

    scatt_cross_section = np.divide(scatt_flux, intensity)
    # Scattering cross section σ =
    # = scattered power in all directions / incident intensity.

    scatt_eff_meep = -1 * scatt_cross_section / (np.pi * d**2)
    # Scattering efficiency =
    # = scattering cross section / cross sectional area of the sphere
    # WATCH IT! Is this the correct cross section?

    freqs = np.array(freqs)

    #%% SAVE FINAL DATA

    data = np.array([1e3 * from_um_factor / freqs, scatt_eff_meep]).T

    header = ["Longitud de onda [nm]", "Sección eficaz efectiva (Meep) [u.a.]"]

    data_base = np.array([
        1e3 * from_um_factor / freqs, box_x1_flux0, box_x1_flux, box_x2_flux,
        box_y1_flux, box_y2_flux, box_z1_flux, box_z2_flux, intensity,
        scatt_flux, scatt_cross_section
    ]).T

    header_base = [
        "Longitud de onda [nm]", "Flujo X10 [u.a.]", "Flujo X1 [u.a]",
        "Flujo X2 [u.a]", "Flujo Y1 [u.a]", "Flujo Y2 [u.a]", "Flujo Z1 [u.a]",
        "Flujo Z2 [u.a]", "Intensidad incidente [u.a.]",
        "Flujo scattereado [u.a.]", "Sección eficaz de scattering [u.a.]"
    ]

    if mp.am_master():
        vs.savetxt(file("Results.txt"), data, header=header, footer=params)
        vs.savetxt(file("BaseResults.txt"),
                   data_base,
                   header=header_base,
                   footer=params)

    #%% PLOT SCATTERING

    if mp.my_rank() == 1:
        plt.figure()
        plt.plot(1e3 * from_um_factor / freqs,
                 scatt_eff_meep,
                 'bo-',
                 label='Meep')
        plt.xlabel('Wavelength [nm]')
        plt.ylabel('Scattering efficiency [σ/πr$^{2}$]')
        plt.legend()
        plt.title(f'Scattering of Au Ellipsoid ({ 1e3*from_um_factor*d }x' +
                  f'{ 1e3*from_um_factor*d }x{ 1e3*from_um_factor*h } nm)')
        plt.tight_layout()
        plt.savefig(file("ScattSpectra.png"))

    #%% PLOT ABSORPTION

    if mp.am_master():
        plt.figure()
        plt.plot(1e3 * from_um_factor / freqs,
                 1 - scatt_eff_meep,
                 'bo-',
                 label='Meep')
        plt.xlabel('Wavelength [nm]')
        plt.ylabel('Absorption efficiency [σ/πr$^{2}$]')
        plt.legend()
        plt.title(f'Absorption of Au Ellipsoid ({ 1e3*from_um_factor*d }x' +
                  f'{ 1e3*from_um_factor*d }x{ 1e3*from_um_factor*h } nm)')
        plt.tight_layout()
        plt.savefig(file("AbsSpectra.png"))