# ============================================================================
#
#                            PUBLIC DOMAIN NOTICE
#
#       National Institute on Deafness and Other Communication Disorders
#
# This software/database is a "United States Government Work" under the 
# terms of the United States Copyright Act. It was written as part of 
# the author's official duties as a United States Government employee and 
# thus cannot be copyrighted. This software/database is freely available 
# to the public for use. The NIDCD and the U.S. Government have not placed 
# any restriction on its use or reproduction. 
#
# Although all reasonable efforts have been taken to ensure the accuracy 
# and reliability of the software and data, the NIDCD and the U.S. Government 
# do not and cannot warrant the performance or results that may be obtained 
# by using this software or data. The NIDCD and the U.S. Government disclaim 
# all warranties, express or implied, including warranties of performance, 
# merchantability or fitness for any particular purpose.
#
# Please cite the author in any work or product based on this material.
# 
# ==========================================================================



# ***************************************************************************
#
#   Large-Scale Neural Modeling software (LSNM)
#
#   Section on Brain Imaging and Modeling
#   Voice, Speech and Language Branch
#   National Institute on Deafness and Other Communication Disorders
#   National Institutes of Health
#
#   This file (netgen.py) was created on May 20 2016.
#
#
#   Author: Antonio Ulloa. Last updated by Antonio Ulloa on May 24 2016
#
#   Based on computer code originally developed by Malle Tagamets and
#   Barry Horwitz (Tagamets and Horwitz, 1998)
# **************************************************************************/

# netgen.py
#
# Reads description of connecton weights among two simulated brain regions
# and generates all the connecton weights between those regions.
# The input, a weight file with extension "w", is given as argument when script
# is executed.
# The ouput is a file of the same name as the input file but with extension
# "ws".
# Only one weight file is processed at a time. If need to process all weight
# files in a directory, you might want to use the following bash script on a
# Unix or Linux terminal:
# for file in *.ws ;
#     do python netgen.py $file ;
# done

import sys

# import regular expression modules (useful for reading weight files)
import re

# import random function modules
import random as rdm

import time as time_module

# First, open file given in the command line (passed as argument)
with open(sys.argv[1], 'r') as f:
    try:
        contents = f.read()
        weights_file = sys.argv[1][:-1]
        print 'Generating weight file', weights_file, '...'

    except IOError:
        print 'Ouch!: For some reason I was unable read your input file (*.ws)'

# intitialize string that will contain weight files:
weights_string = []

# Scan the input weight file to find parameters of connection weights among brain regions, and save those
# parameters to relevant variables
parameters = re.match(r'(.*) (.*) SV I\((.*) (.*)\) O\((.*) (.*)\) F\((.*) (.*)\) (.*) (.*) Offset', contents)
InSet = parameters.group(1)
OutSet = parameters.group(2)
ix      = parameters.group(3)
iy      = parameters.group(4)
ox      = parameters.group(5)
oy      = parameters.group(6)
fx      = parameters.group(7)
fy      = parameters.group(8)
seed    = parameters.group(9)
pctzero = parameters.group(10)

# Now scan input weights file to find the actual weights and standard error of those weights (base and scale),
# and save all those weights to relevant python lists.
weights = re.findall(r'[+-]?\d+\.\d+:', contents) 
errors  = re.findall(r':\d+.\d+', contents)

# initialize base and scale
base = []
scale = []

# now, get rid of ":" in base and scale and convert base and scale to float
for b in weights:
    b = float(b[:-1])
    base.append(b)

for s in errors:
    s = float(s[1:]) 
    scale.append(s*2)

# convert numeric strings to int prior to making operations with them
ix = int(ix)
iy = int(iy)
ox = int(ox)
oy = int(oy)
fx = int(fx)
fy = int(fy)
seed = int(seed)
pctzero = float(pctzero)

istartx = 0
istarty = 0

ostartx = 0
ostarty = 0

idx = 1
idy = 1
odx = 1
ody = 1

# grab date and time from the system:
start_time = time_module.asctime(time_module.localtime(time_module.time()))

# Update final weights string with date and network information
weights_string  = "% " + start_time + "\n\n"
weights_string += "% Input Layer: "  + "(" + str(ix) + ", " + str(iy) + ")" + "\n"
weights_string += "% Output Layer: " + "(" + str(ox) + ", " + str(oy) + ")" + "\n"
weights_string += "% Fanout Size: " + "(" + str(fx) + ", " + str(fy) + ")" + "\n\n"
weights_string += "Connect(" + InSet + ", " + OutSet + ")  {\n"

row=0
col=0

# Initialize random number generator seed using system time
rdm.seed()

for i in range(istartx, ix, idx):
    for j in range(istarty, iy, idy):
        weights_string += "  From:  (" + str(i+1) + ", " + str(j+1) + ")  {\n"

        row = (ostartx - fx / 2 + ox) % ox
        col = (ostarty - fy / 2 + oy) % oy

        k = 0
        n_weights = 0

        for orow in range(0, fx, 1):
            for ocol in range(0, fy, 1):
                
                outx = (row + orow) % ox
                outy = (col + ocol) % oy

                x = rdm.random()
        
                if (x < pctzero) or (base[k] == 0.0):
                    weights_string += "    |              | "
                else:
                    x = rdm.random() - 0.5
                    x = base[k] + x * scale[k]
                    x = "{:.6f}".format(x)
                    weights_string += "    ([ " + str(outx+1) + ", " + str(outy+1) + "]  " + str(x) + ") "
                    n_weights = n_weights + 1

                k = k + 1
            weights_string += "\n"

        # make sure we have at least one outgoing set of weights
        if (pctzero < 1.0 and n_weights == 0):
            k = 0
            outx = i / ox
            outy = j / oy
            x = rdm.random() - 0.5
            x = base[k] + x * scale[k]
            x = "{:.6f}".format(x)
            weights_string += "    ([ " + str(outx+1) + ", " + str(outy+1) + "]  " + str(x) + ") "
            
        weights_string += "  }\n"

        ostarty = ostarty + ody

    ostartx = ostartx + odx

# insert a final closing curly bracket after the final set of weights
weights_string += "}\n"

# now, create a new weight file to write the generated weights to...
with open(weights_file, 'w') as f:
    try:
      f.write(weights_string)
    except IOError:
        print 'Ouch!: For some reason I cannot write to the output file (*.w)'