"""
The script used to build the network for the simulation run in run_network.py
"""
import os,sys,inspect
import shutil
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0, parentdir) 
sys.path.insert(0, currentdir)

from bmtk.builder import NetworkBuilder
from bmtk.utils.sim_setup import build_env_bionet
import numpy as np
import sys
import synapses   
import h5py
import pandas as pd   
import json   
import scipy.stats as st
from functools import partial

from raster_maker import *
from clustering import *

try:
    np.random.seed(int(sys.argv[-1]))
except:
    np.random.seed(123)

def delete_file(filename):
        try:
                os.remove(filename)
                print(filename + " sucessfully deleted.")
        except:
                print(filename + " not found for deletion.")

def delete_folder(dir_name):
        try:
                shutil.rmtree(dir_name)
                print(dir_name + " sucessfully deleted.")
        except:
                print(dir_name + " not found for deletion.")


def clean_folder():
        delete_file("./node_sets.json")
        delete_file("./run_bionet.py")
        delete_file("./simulation_config.json")
        delete_file("./prox_inh_stim_spikes.h5")
        delete_file("./exc_stim_spikes.h5")
        delete_file("./dist_inh_stim_spikes.h5")
        delete_file("./circuit_config.json")
        delete_folder("./network")
        delete_folder("./output")
        delete_folder("./__pycache__")

class SimulationBuilder:
        """Class used to build our BMTK simulation.

        Attributes
        ----------
        params : dict
                contains parameters for the network
        seed : int
                base random seed for the simulation
        syn : dict
                contains synaptic templates

        n_dend_exc : int
                number of excitatory input cells on the basal dendrites
        n_apic_exc : int
                number of excitatory input cells on the apical dendrites

        n_dend_inh : int
                number of inhibitory (SOM+) input cells on the basal dendrites 
                more than 50 um from the soma.
        n_apic_inh : int
                number of inhibitory (SOM+) input cells on the apical dendrites
        n_prox_dend_inh : int
                number of inhibitory (PV+) input cells on the basal dendrites
                less than 50 um from the soma
        n_soma_inh : int
                number of inhibitory (PV+) input cells on the soma

        clust_per_group : int
                number of clusters per functional group

        net : NetworkBuilder
                the BMTK network for the biophysical cell
        exc_stim : NetworkBuilder
                the BMTK network for excitatory inputs
        prox_inh_stim : NetworkBuilder
                the BMTK network for perisomatic inhibition
        dist_inh_stim : NetworkBuilder
                the BMTK network for dendritic inhibition

        dend_groups : list
                all excitatory functional groups on the basal dendrites
        apic_groups : list
                all excitatory functional groups on the apical dendrites

        Methods
        -------
        build()
                builds the network
        save_groups()
                saves the functional groups to a csv

        _set_prefixed_directory(base_dir_name : str)
                sets up the correct biophy_components structure based on the cell prefix in params for the given directory base

        _build_exc()
                creates excitatory input nodes and edges
        _build_exc_nodes(segs : pandas.DataFrame, base_name : str, n_cells : int, start=0 : int)
                builds excitatory nodes
        _build_exc_edges(group_list : list)
                builds excitatory edges

        _save_nets()
                builds and saves the BMTK NetworkBuilders

        _build_inh()
                creates inhibitory input nodes and edges
        
        _make_rasters()
                creates the inhibitory and excitatory input rasters
        _gen_exc_spikes(fname : str)
                generates and saves the excitatory spike rasters
        _gen_inh_spikes(n_cells : int, mean_fr : float, std_fr : float, key : str, fname : str)
                creates inhibitory spike rasters, using a noise trace based on averaging excitation and shifting it
        _modify_jsons()
                modifies the various json files however is needed after they are built
        _modify_sim_config()
                modifies the simulation_config.json however is needed
        _update_cellvar_record_locs(sim_config : dict)
                modifies the location of cellvar recordings in the given JSON simulation_config

        Static Methods
        --------------
        _get_directory_prefix(directory : str)
                reads the prefix.txt fil in directory and returns the contents
        _connector_func(sources : list, targets : list, cells : list)
                sets the number of synapses from the given cells
        _set_location(source : dict, target : dict, cells : list, start_id : int)
                sets the location of the given edge

        _norm_connect(source : dict, target : dict, m : float, s : float, low : int, high : int)
                used to normally distribute connection counts

        _gen_group_spikes(writer : SonataWriter, group : FunctionalGroup, seconds : float, start_time : float, dist : func)
                creates and saves a functional group's spike raster
        _norm_rvs(mean : float, std : float)
                generates a random float from a normal distribution with a near zero minimum
        """

        def __init__(self, params_file, seed=123):
                """Initializes the simulation builder, 
                setting up attributes but not actually building the BMTK network.

                Parameters
                ----------
                params_file : str
                    path to the JSON file with network parameters
                seed : int
                    base random seed for the simulation
                """     
                #Loads the JSON file with information about the network.           
                with open(params_file) as f:
                        self.params = json.load(f)

                self.seed = seed

                #Loads synapse templates.
                synapses.load()
                self.syn = synapses.syn_params_dicts()

                avg_exc_div = np.mean(list(self.params["divergence"]["exc"].values()))

                self.n_dend_exc = int((self.params["lengths"]["basal_dist"] * self.params["syn_density"]["exc"]) / avg_exc_div)
                
                self.n_apic_exc = int((self.params["lengths"]["apic"] * self.params["syn_density"]["exc"]) / avg_exc_div)

                self.n_dend_inh = int((self.params["lengths"]["basal_dist"] * self.params["syn_density"]["inh"]) / self.params["divergence"]["basal_inh"]["m"])
                self.n_apic_inh = int((self.params["lengths"]["apic"] * self.params["syn_density"]["inh"]) / self.params["divergence"]["apic_inh"]["m"])

                self.n_prox_dend_inh = int((self.params["lengths"]["basal_prox"] * self.params["syn_density"]["inh"]) / self.params["divergence"]["peri_inh"]["m"])
                self.n_soma_inh = int(self.params["n_soma_syns"] / self.params["divergence"]["peri_inh"]["m"])

                self.clust_per_group = int((self.params["groups"]["cells_per_group"] * avg_exc_div) // (self.params["syn_density"]["exc"] * 10))
                if self.params["file_current_clamp"]["input_file"]=="None": 
                    self.file_current_clamp = None
                else:
                    self.file_current_clamp = self.params["file_current_clamp"]

        def build(self):
                """Builds the nodes and edges for the network.
                """                
                np.random.seed(self.seed)

                self._set_prefixed_directory("mechanisms")
                self._set_prefixed_directory("templates")

                self.net = NetworkBuilder("biophysical")

                self.net.add_nodes(N=1, pop_name='Pyrc',
                        potental='exc',
                        model_type='biophysical',
                        dynamics_params= self.params["cell"]["dynamic_params"],
                        model_template= self.params["cell"]["model_template"],
                        model_processing = self.params["cell"]["model_processing"],
                        morphology = self.params["cell"]["morphology"])

                self._build_exc()
                self._build_inh()
                self._save_nets()

                self._make_rasters()
                
                #Final build step.
                build_env_bionet(base_dir='./',
                        network_dir='./network',
                        dt = self.params["dt"], 
                        tstop=self.params["time"]["stop"] * 1000.0,
                        report_vars=self.params["record_cellvars"]["vars"],
                        dL = self.params["dL"],#target length (um) of segments
                        #file_current_clamp={
                        #    "input_file": "invivoRin.h5"
                        #},
			#current_clamp = {
		 	#	'amp': -1,
			#	'delay': 500,
			#	'duration': 1000,
			#	'gids': "all"
			#},
			spikes_threshold=-10,
                        spikes_inputs=[('exc_stim', 'exc_stim_spikes.h5'), ('prox_inh_stim', 'prox_inh_stim_spikes.h5'), ('dist_inh_stim', 'dist_inh_stim_spikes.h5')],
                        components_dir='../biophys_components',
                        compile_mechanisms=True)

                self._modify_jsons()

        def save_groups(self):
                """saves the apic and dend groups into a csv.
                one row for each node containgin the id of the functional group it is in.
                """        
                all_groups = self.dend_groups + self.apic_groups
                node_ids = []
                func_groups = []

                for func_id, group in enumerate(all_groups):
                        for i in range(group.start_id, group.start_id + group.n_cells):
                                node_ids.append(i)
                                func_groups.append(func_id)

                df = pd.DataFrame()
                df["Node ID"] = node_ids
                df["Functional Group"] = func_groups
                df.to_csv("FunctionalGroups.csv", index=False)  

        def _set_prefixed_directory(self, base_dir_name):
                """Fixes the biophy_components directory. There should be only one directory
                named <base_dir_name> and it should be the one with the prefix.txt file in it
                that has the same prefix as params.

                Parameters
                ----------
                base_dir_name : str
                        base name of the set of directories to be fixed
                """
                #import pdb; pdb.set_trace()
                components_path = "../biophys_components/"
                biophys_subdirs = [ f.name for f in os.scandir(components_path) if f.is_dir() ]
                
                for dir_name in biophys_subdirs:
                        if base_dir_name == dir_name:
                                prefix = SimulationBuilder._get_directory_prefix(components_path + dir_name)
                                if prefix == self.params["cell"]["prefix"]:
                                        return
                                else:
                                        os.rename(components_path + base_dir_name, components_path + prefix + base_dir_name)
                                        


                for dir_name in biophys_subdirs:
                        if base_dir_name in dir_name and self.params["cell"]["prefix"] in dir_name:
                                os.rename(components_path + dir_name, components_path + base_dir_name)

        def _get_directory_prefix(directory):
                """Returns the contents of the prefix.txt file in the given directory.

                Parameters
                ----------
                directory : str
                        directory to look in

                Returns
                -------
                str
                        contents of prefix.txt
                """  
                with open(directory + "/prefix.txt", 'r') as f:
                        return f.read()

        def _build_exc(self):
                """Builds the excitatory input cells and their synapses.
                """          

                # External excitatory inputs
                self.exc_stim = NetworkBuilder('exc_stim')

                #DataFrame of all segments on the cell.
                segs = pd.read_csv(self.params["cell"]["segments_file"])

                dends = segs[(segs["Type"] == "dend") & (segs["Distance"] >= 50)]
                apics = segs[(segs["Type"] == "apic")]

                np.random.seed(self.seed + 1)
                apic_start, self.dend_groups = self._build_exc_nodes(dends, "dend", self.n_dend_exc)
                
                np.random.seed(self.seed + 2)
                _, self.apic_groups = self._build_exc_nodes(apics, "apic", self.n_apic_exc, start=apic_start)

                np.random.seed(self.seed + 3)
                self._build_exc_edges(self.dend_groups)

                np.random.seed(self.seed + 4)
                self._build_exc_edges(self.apic_groups)

        #Sets the number of synapses for each input cell.
        def _connector_func(sources, target, cells):
                """Used to set the number of synapses from each excitatory input
                cell in a functional group. Use with "all_to_one" iterator.

                Parameters
                ----------
                sources : list
                        presynaptic nodes (represented as dicts)
                target : dict
                        postsynaptic node
                cells : list
                        list of Cells in the FunctionalGroup

                Returns
                -------
                list
                        list of synapses for each pairing
                """
                return [cell.n_syns for cell in cells]

        #Sets the location of synapses based on the given cell list.
        def _set_location(source, target, cells, start_id):
                """Sets the location of the given synapse.

                Parameters
                ----------
                source : dict
                    source node information
                target : dict
                    target node information
                cells : list
                    Cells in the functional group
                start_id : int
                    start_id for the functional groups the cells come from

                Returns
                -------
                int
                    BMTK section id
                float
                    distance along the section
                """     
                #Gets the proper index within the cell list.           
                index = source.node_id - start_id

                seg = cells[index].get_seg()
                return seg.bmtk_id, seg.x

        #Creates the functional groups and adds the virtual cells to the
        #BMTK NetworkBuilder.
        def _build_exc_nodes(self, segs, base_name, n_cells, start=0):
                """Creates the functional groups and adds the virtual cells to the
                BMTK NetworkBuilder

                Parameters
                ----------
                segs : pandas.DataFrame
                    all the segments available for the functional groups
                base_name : str
                    the string that is appended to to make the group names.
                    groups get 0 - n_groups appended to their names.
                n_cells : int
                    total number of input cells that should be added.
                start : int, optional
                    starting id to be associated with the functional groups, by default 0
                    this is used later to associate cells in functional groups with the correct
                    locations and synapses.

                Returns
                -------
                int
                    what the start parameter should be for the next call to _build_exc_nodes
                list
                    list of functional groups that were created
                """ 
                start_id = start

                n_groups = n_cells // self.params["groups"]["cells_per_group"]
                n_extra = n_cells % self.params["groups"]["cells_per_group"] #number of extra cells that don't evenly fit into groups

                group_list = []

                for i in range(n_groups):
                        name = base_name + str(i)

                        #Spreads out the extra cells.
                        N = self.params["groups"]["cells_per_group"]
                        if i < n_extra:
                                N += 1

                        self.exc_stim.add_nodes(N=N,
                                pop_name=name,
                                potential="exc",
                                model_type='virtual')

                        new_group = FunctionalGroup(segs, segs.sample().iloc[0],
                                N, self.clust_per_group, name, start_id, 
                                partial(make_seg_sphere, radius = self.params["groups"]["group_radius"]), 
                                partial(make_seg_sphere, radius = self.params["groups"]["cluster_radius"]))
                        group_list.append(new_group)
                        start_id += N

                return start_id, group_list

        def _build_exc_edges(self, group_list):
                """Creates the connections between each cell in the list of groups
                and the biophysical cell.

                Parameters
                ----------
                group_list : list
                    list of functional groups
                """                
                for i in range(len(group_list)):
                        group = group_list[i]

                        #Creates the edges from each excitatory input cells in the group.
                        conn = self.net.add_edges(source=self.exc_stim.nodes(pop_name=group.name), target=self.net.nodes(),
                                iterator="all_to_one",
                                connection_rule=SimulationBuilder._connector_func,
                                connection_params={'cells': group.cells},
                                syn_weight=1,
                                delay=0.1,
                                dynamics_params='PN2PN.json',
                                model_template=self.syn['PN2PN.json']['level_of_detail'],)

                        #Sets the postsynaptic locations of the connections.
                        conn.add_properties(['sec_id',"sec_x"], 
                                rule=SimulationBuilder._set_location,
                                rule_params={'cells': group.cells, 'start_id': group.start_id},
                                dtypes=[np.int, np.float])


        def _save_nets(self):
                """builds and saves the BMTK NetworkBuilders
                """                
                # Build and save our networks
                np.random.seed(self.seed + 12)
                self.net.build()
                self.net.save_nodes(output_dir='network')
                np.random.seed(self.seed + 16)
                self.net.save_edges(output_dir='network')

                np.random.seed(self.seed + 13)
                self.exc_stim.build()
                self.exc_stim.save_nodes(output_dir='network')

                np.random.seed(self.seed + 14)
                self.prox_inh_stim.build()
                self.prox_inh_stim.save_nodes(output_dir='network')

                np.random.seed(self.seed + 15)
                self.dist_inh_stim.build()
                self.dist_inh_stim.save_nodes(output_dir='network')


        def _build_inh(self):
                """Creates inhibitory input nodes and their connections onto the biophysical cell
                """     

                #####################Perisomatic Inhibition##############################
                self.prox_inh_stim = NetworkBuilder('prox_inh_stim')

                #Nodes that connect to soma.
                self.prox_inh_stim.add_nodes(N=self.n_soma_inh,
                                pop_name='on_soma',
                                potential='exc',
                                model_type='virtual')
                
                #Nodes that connect to proximal dendrites.
                self.prox_inh_stim.add_nodes(N=self.n_prox_dend_inh,
                                pop_name='on_dend',
                                potential='exc',
                                model_type='virtual')

                div_params = self.params["divergence"]["peri_inh"]

                #On soma.
                np.random.seed(self.seed + 5)
                self.net.add_edges(source=self.prox_inh_stim.nodes(pop_name='on_soma'),
                                target=self.net.nodes(),
                                connection_rule=SimulationBuilder._norm_connect,
                                connection_params={"m":div_params["m"], "s":div_params["s"], "low":div_params["min"], "high":div_params["max"]},
                                syn_weight=1,
                                delay=0.1,
                                dynamics_params='PV2PN.json',
                                model_template=self.syn['PV2PN.json']['level_of_detail'],
                                distance_range=[-2000, 2000.0],
                                target_sections=['somatic'])

                #On dendrites within 50 um
                np.random.seed(self.seed + 6)
                self.net.add_edges(source=self.prox_inh_stim.nodes(pop_name='on_dend'), 
                                target=self.net.nodes(),
                                connection_rule=SimulationBuilder._norm_connect,
                                connection_params={"m":div_params["m"], "s":div_params["s"], "low":div_params["min"], "high":div_params["max"]},
                                syn_weight=1,
                                delay=0.1,
                                dynamics_params='PV2PN.json',
                                model_template=self.syn['PV2PN.json']['level_of_detail'],
                                distance_range=[0, 50.0],
                                target_sections=['dend'])
                #######################################################################################

                #############################Dendritic Inhibition######################################
                self.dist_inh_stim = NetworkBuilder('dist_inh_stim')

                self.dist_inh_stim.add_nodes(N=self.n_dend_inh,
                                pop_name='dend',
                                potential='exc',
                                model_type='virtual')

                self.dist_inh_stim.add_nodes(N=self.n_apic_inh,
                                pop_name='apic',
                                potential='exc',
                                model_type='virtual')

                div_params = self.params["divergence"]["basal_inh"]

                #Basal edges.
                np.random.seed(self.seed + 7)
                self.net.add_edges(source=self.dist_inh_stim.nodes(pop_name="dend"),
                                target=self.net.nodes(),
                                connection_rule=SimulationBuilder._norm_connect,
                                connection_params={"m":div_params["m"], "s":div_params["s"], "low":div_params["min"], "high":div_params["max"]},
                                syn_weight=1,
                                delay=0.1,
                                dynamics_params='SOM2PN.json',
                                model_template=self.syn['SOM2PN.json']['level_of_detail'],
                                distance_range=[50, 2000.0],
                                target_sections=['dend'])
                
                div_params = self.params["divergence"]["apic_inh"]

                #Apic edges.
                np.random.seed(self.seed + 8)
                self.net.add_edges(source=self.dist_inh_stim.nodes(pop_name="apic"), 
                                target=self.net.nodes(),
                                connection_rule=SimulationBuilder._norm_connect,
                                connection_params={"m":div_params["m"], "s":div_params["s"], "low":div_params["min"], "high":div_params["max"]},
                                syn_weight=1,
                                delay=0.1,
                                dynamics_params='SOM2PN.json',
                                model_template=self.syn['SOM2PN.json']['level_of_detail'],
                                distance_range=[50, 2000.0],
                                target_sections=['apic'])

        def _norm_connect(source, target, m, s, low, high):
                """Returns a random number of synapses based on
                the given distribution.

                Parameters
                ----------
                source : dict
                    source node
                target : dict
                    target node
                m : float
                    mean number of connections
                s : float
                    standard deviation of number of connections
                low : int
                    minimum number of connections
                high : int
                    maximum number of connections

                Returns
                -------
                int
                    number of connections
                """                
                return int(min(max(np.random.normal(m, s), low), high))

            

        def _make_rasters(self):
                """Generates excitatory and inhibitory input rasters
                """    
                np.random.seed(self.seed + 9)

                self._gen_exc_spikes('exc_stim_spikes.h5')

                inh_frs = self.params["inh_frs"]

                #Makes perisomatic inhibitory raster.
                np.random.seed(self.seed + 10)
                self._gen_inh_spikes(self.n_soma_inh + self.n_prox_dend_inh, 
                                     inh_frs["proximal"]["m"], 
                                     inh_frs["proximal"]["s"], 
                                     inh_frs["proximal"]["rhythmicity"],
                                     "prox_inh_stim", 
                                     'prox_inh_stim_spikes.h5')
                
                #Makes dendritic inhibitory raster.
                np.random.seed(self.seed + 11)
                self._gen_inh_spikes(self.n_apic_inh + self.n_dend_inh, 
                                     inh_frs["distal"]["m"], 
                                     inh_frs["distal"]["s"],
                                     inh_frs["distal"]["rhythmicity"],
                                     "dist_inh_stim", 
                                     'dist_inh_stim_spikes.h5')


        #Generates the spike raster for a given group.
        #The group has the same noise.
        def _gen_group_spikes(writer, group, seconds, start_time, dist):
                """Generates and writes to a h5 file the given functional group's spike trains

                Parameters
                ----------
                writer : SonataWriter
                    how the spike trains are saved
                group : FunctionalGroup
                    the functional group that the spike trains are being made for
                seconds : float
                    length of the spike trains in seconds
                start_time : float
                    what time (ms) the spike trains should start at
                dist : func
                    function for random distribution used for an individual cell's firing rate
                """                
                z = make_noise(num_samples=(int(seconds*1000))-1,num_traces=1)#generates the noise trace common to each cell in the functional group.
                make_save_spikes(writer, True, dist(size=group.n_cells), numUnits=group.n_cells,
                        rateProf=np.tile(z[0,:],(group.n_cells,1)),start_id=group.start_id,
                        start_time=start_time)

        #Creates the excitatory input raster from the functional groups.
        def _gen_exc_spikes(self, fname):
                """Generates the excitatory input raster for all of the functional groups

                Parameters
                ----------
                fname : str
                    name of the file to save the rasters in (.h5)
                """    
                #distribution used for generating excitatory firing rates.    
                levy_dist = partial(st.levy_stable.rvs, alpha=1.37, beta=-1.00, loc=0.92, scale=0.44, size=1)

                length = self.params["time"]["stop"] - self.params["time"]["start"]
                buffer = self.params["time"]["start"]

                writer = SonataWriter(fname, ["spikes", "exc_stim"], ["timestamps", "node_ids"], [np.float, np.int])

                for group in (self.dend_groups + self.apic_groups):
                        SimulationBuilder._gen_group_spikes(writer, group, length, buffer*1000, levy_dist)
                

        #Blocks off the bottom of a normal distribution.
        def _norm_rvs(mean, std):
                """Generates a random float from a normal distribution with a near zero minimum

                Parameters
                ----------
                mean : float
                    mean of the distribution
                std : float
                    standard deviation of the distribution

                Returns
                -------
                float
                    random float
                """                
                return max(st.norm.rvs(loc=mean, scale=std, size=1), 0.001)

        # #Makes a spike raster with each cell having its own noise trace.
        # def gen_inh_spikes(n_cells, mean_fr, std_fr, key, file, times):
        #         # node_ids = []
        #         # timestamps = []

        #         length = times[1] - times[0]
        #         buffer = times[0]

        #         writer = SonataWriter(file, ["spikes", key], ["timestamps", "node_ids"], [np.float, np.int])

        #         z = make_noise(num_samples=(int(length*1000))-1,num_traces=1)
        #         make_save_spikes(writer, False, partial(positive_normal, mean=mean_fr, std=std_fr), numUnits=n_cells,rateProf=z[0,:],start_time=buffer*1000)

        #Creates a spike raster with each cell having the same noise coming from the a shifted average of excitation.
        def _gen_inh_spikes(self, n_cells, mean_fr, std_fr, rhythmic_dict, key, fname):
                """Generates a spike raster with each train having the noise trace from
                averaging excitation. Distributes firing rates normally.

                Parameters
                ----------
                n_cells : int
                    number of spike trains
                mean_fr : float
                    mean firing rate
                std_fr : float
                    standard deviation of the firing rate
                rhythmic_dict : dict
                    dictionary with keys f - frequency, mod - depth of modulation
                key : str
                    name of the second group in the h5 file
                fname : str
                    name of file to save the raster to
                """                
                # node_ids = []
                # timestamps = []
                a, b = (0 - mean_fr) / std_fr, (100 - mean_fr) / std_fr
                d = partial(st.truncnorm.rvs, a=a, b=b, loc=mean_fr, scale=std_fr)
                
                if rhythmic_dict['f'] == "None":
                    f = h5py.File("exc_stim_spikes.h5", "r")
                    ts = f['spikes']["exc_stim"]['timestamps']
                    nid = f['spikes']["exc_stim"]['node_ids']

                    #Creates a noise trace based on the excitatory spike raster.
                    z = shift_exc_noise(ts, nid, self.params["time"]["stop"], time_shift=self.params["inh_shift"])
                    z = np.tile(z,(n_cells,1))
                    
                    writer = SonataWriter(fname, ["spikes", key], ["timestamps", "node_ids"], [np.float, np.int])
                    make_save_spikes(writer, False, d(size=n_cells), numUnits=n_cells,rateProf=z)

                else:
                    # make an array of modulated sin waves
                    # make_save_spikes should be written so that the firing rates are generated
                    #    outside instead of inside the function.
                    frs = d(size=n_cells)
                    
                    t = np.arange(0,self.params["time"]["stop"],0.001)
                    z = np.zeros((n_cells,t.shape[0]))
                    P = 0
                    for i in np.arange(0,n_cells):
                        offset = frs[i]
                        A = offset/((1/rhythmic_dict['mod'])-1)
                        z[i,:] = A*np.sin((2 * np.pi * rhythmic_dict['f'] * t)+P) + offset

                    writer = SonataWriter(fname, ["spikes", key], ["timestamps", "node_ids"], [np.float, np.int])
                    make_save_spikes(writer, False, np.ones((n_cells,1)), numUnits=n_cells,rateProf=z)

        def _modify_jsons(self):
                """modifies the various json files however is needed after they are built"""
                self._modify_sim_config()

        def _modify_sim_config(self):
                """modifies the simulation_config.json however is needed"""
                with open("simulation_config.json", "r") as jsonFile:
                        sim_config = json.load(jsonFile)

                self._update_cellvar_record_locs(sim_config)

                with open("simulation_config.json", "w") as jsonFile:
                        json.dump(sim_config, jsonFile, indent=2)

        def _update_cellvar_record_locs(self, sim_config):
                """modifies the location of cellvar recordings in the given JSON simulation_config
                
                Parameters
                ----------
                sim_config : dict
                    simulation_config to modify
                """
                reports = sim_config["reports"]
                cellvar_reports = [report for report in reports.values() if report["module"] == "membrane_report"]

                for loc, report in zip(self.params["record_cellvars"]["locs"], cellvar_reports):
                        report["sections"] = loc

if __name__ == "__main__":
        try:
                net_params = sys.argv[1]
        except:
                net_params = "L5NetParams.json"

        if ".json" not in net_params:
                net_params = "L5NetParams.json"

        clean_folder()

        builder = SimulationBuilder(net_params)
        builder.build()
        builder.save_groups()