def minusLW(W):
            def compute_sq_error(a, b, wt):
                return np.sum(wt * (a - b)**2)

            def compute_kl_error(mu_data, pc_list, mu_model, fprimeval, wt):
                # how to model variability in X?
                kl = compute_kl_divergence(fprimeval, noise, mu_data, mu_model,
                                           pc_list)
                return kl  #wt*kl
                # principled way would be to use 1/wt for noise term. Should add later.

            def compute_opto_error_nonlinear(W, wt=None):
                if wt is None:
                    wt = np.ones((2 * nN, nQ * nS * nT))
                Wmx, Wmy, Wsx, Wsy, s02, K, kappa, T, XX, XXp, Eta, Xi, h1, h2, Eta1, Eta2 = parse_W(
                    W)
                Eta12 = np.concatenate((Eta1, Eta2), axis=0)
                Xi12 = np.concatenate((Xi, Xi), axis=0)
                XX12 = np.concatenate((XX, XX), axis=0)
                fval12 = compute_f_(Eta12, Xi12, s02)

                fval = compute_f_(Eta, Xi, s02)
                dYY12 = fval12 - np.concatenate((fval, fval), axis=0)
                dYYterm = np.sum(wt[opto_mask] *
                                 (dYY12[opto_mask] - dYY[opto_mask])**2)

                dHH = np.zeros((nN, nQ * nS * nT))
                dHH[:, np.arange(2, nQ * nS * nT, nQ)] = 1
                dHH = np.concatenate((dHH * h1, dHH * h2), axis=0)
                Eta12perf = u_fn(XX12, fval12, Wmx, Wmy, K, kappa, T) + dHH
                Eta12term = np.sum(wt * (Eta12perf - Eta12)**2)

                #cost = wtdYY*dYYterm + wtEta12*Eta12term
                return dYYterm, Eta12term  #cost

            def compute_opto_error_nonlinear_transform(W, wt=None):
                if wt is None:
                    wt = np.ones((2 * nN, nQ * nS * nT))
                Wmx, Wmy, Wsx, Wsy, s02, K, kappa, T, XX, XXp, Eta, Xi, h1, h2, Eta1, Eta2 = parse_W(
                    W)
                Eta12 = np.concatenate((Eta1, Eta2), axis=0)
                Xi12 = np.concatenate((Xi, Xi), axis=0)
                XX12 = np.concatenate((XX, XX), axis=0)
                fval12 = compute_f_(Eta12, Xi12, s02)

                fval = compute_f_(Eta, Xi, s02)
                #fvalrep = np.concatenate((fval,fval),axis=0)
                #dYY12 = fval12 - fvalrep
                fval12target = np.concatenate(
                    (opto_transform1.transform(fval),
                     opto_transform2.transform(fval)),
                    axis=0)
                #this_dYY = fval12target - fvalrep

                dYYterm = np.sum(
                    wt[opto_mask] *
                    (fval12[opto_mask] - fval12target[opto_mask])**2)

                dHH = np.zeros((nN, nQ * nS * nT))
                dHH[:, np.arange(2, nQ * nS * nT, nQ)] = 1
                dHH = np.concatenate((dHH * h1, dHH * h2), axis=0)
                Eta12perf = u_fn(XX12, fval12, Wmx, Wmy, K, kappa, T) + dHH
                Eta12term = np.sum(wt * (Eta12perf - Eta12)**2)

                #cost = wtdYY*dYYterm + wtEta12*Eta12term
                return dYYterm, Eta12term  #cost

            def compute_coupling(W):
                Wmx, Wmy, Wsx, Wsy, s02, K, kappa, T, XX, XXp, Eta, Xi, h1, h2, Eta1, Eta2 = parse_W(
                    W)
                WWy = gen_Weight(Wmy, K, kappa, T)
                Phi = fprime_m(Eta, compute_var(Xi, s02))
                Phi = np.concatenate((Phi, Phi), axis=0)
                Phi1 = np.array([np.diag(phi) for phi in Phi])
                coupling = np.array([
                    phi1 @ np.linalg.inv(np.eye(nQ * nS * nT) - WWy @ phi1)
                    for phi1 in Phi1
                ])
                return coupling

            def compute_coupling_error(W, i, j, sgn=-1):
                # constrain coupling term i,j to have a specified sign,
                # -1 for negative or +1 for positive
                coupling = compute_coupling(W)
                log_arg = sgn * coupling[:, i, j]
                cost = utils.minus_sum_log_ceil(log_arg, big_val / nN)
                return cost

            #def compute_eta_tv(this_Eta):
            #    Etar = this_Eta.reshape((nsize,ncontrast,nQ*nS*nT))
            #    diff_size = np.sum(np.abs(np.diff(Etar,axis=0)))
            #    diff_contrast = np.sum(np.abs(np.diff(Etar,axis=1)))
            #    return diff_size + diff_contrast

            def compute_isn_error(W):
                Wmx, Wmy, Wsx, Wsy, s02, K, kappa, T, XX, XXp, Eta, Xi, h1, h2, Eta1, Eta2 = parse_W(
                    W)
                Phi = fprime_m(Eta, compute_var(Xi, s02))
                #print('min Eta: %f'%np.min(Eta[:,0]))
                #print('WEE: %f'%Wmy[0,0])
                #print('min phiE*WEE: %f'%np.min(Phi[:,0]*Wmy[0,0]))
                log_arg = Phi[:, 0] * Wmy[0, 0] - 1
                cost = utils.minus_sum_log_ceil(log_arg, big_val / nN)
                #print('ISN cost: %f'%cost)
                return cost

            def compute_tv_error(W):
                # sq l2 norm for tv error
                Wmx, Wmy, Wsx, Wsy, s02, K, kappa, T, XX, XXp, Eta, Xi, h1, h2, Eta1, Eta2 = parse_W(
                    W)
                topo_var_list = [arr.reshape(topo_shape+(-1,)) for arr in \
                        [XX,XXp,Eta,Xi,Eta1,Eta2]]
                sqdiffy = [
                    np.sum(np.abs(np.diff(top, axis=0))**2)
                    for top in topo_var_list
                ]
                sqdiffx = [
                    np.sum(np.abs(np.diff(top, axis=1))**2)
                    for top in topo_var_list
                ]
                cost = np.sum(sqdiffy + sqdiffx)
                return cost

            Wmx, Wmy, Wsx, Wsy, s02, K, kappa, T, XX, XXp, Eta, Xi, h1, h2, Eta1, Eta2 = parse_W(
                W)

            #utils.print_labeled('T',T)
            #utils.print_labeled('K',K)
            #utils.print_labeled('Wmy',Wmy)

            perturbation = perturbation_size * np.random.randn(*Eta.shape)

            #             fval,fprimeval = compute_f_fprime_t_(W,perturbation) # Eta the mean input per cell, Xi the stdev. input per cell, s02 the baseline variability in input
            fval, fprimeval = compute_f_fprime_t_avg_(
                W, perturbation
            )  # Eta the mean input per cell, Xi the stdev. input per cell, s02 the baseline variability in input
            #utils.print_labeled('fval',fval)

            Xterm = compute_kl_error(XXhat, Xpc_list, XX, XXp, wtStim *
                                     wtInp)  # XX the modeled input layer (L4)
            Yterm = compute_kl_error(
                YYhat, Ypc_list, fval, fprimeval,
                wtStim * wtCell)  # fval the modeled output layer (L2/3)
            Etaterm = compute_sq_error(
                Eta, u_fn(XX, fval, Wmx, Wmy, K, kappa, T),
                wtStim * wtCell)  # magnitude of fudge factor in mean input
            Xiterm = compute_sq_error(
                Xi, u_fn(XX, fval, Wsx, Wsy, K, kappa, T), wtStim *
                wtCell)  # magnitude of fudge factor in input variability
            # returns value float
            #Optoterm = compute_opto_error_nonlinear(W) #testing out 8/20/20
            opto_wt = np.concatenate(
                [wtStimOpto * wtCellOpto * w for w in wtDirOpto], axis=0)
            if use_opto_transforms:
                dYYterm, Eta12term = compute_opto_error_nonlinear_transform(
                    W, opto_wt)
            else:
                dYYterm, Eta12term = compute_opto_error_nonlinear(W, opto_wt)
            Optoterm = wtdYY * dYYterm + wtEta12 * Eta12term
            #EtaTVterm = 0
            #for this_Eta in [Eta,Eta1,Eta2]:
            #    EtaTVterm = EtaTVterm + compute_eta_tv(this_Eta)
            cost = wtX * Xterm + wtY * Yterm + wtEta * Etaterm + wtXi * Xiterm + wtOpto * Optoterm  # + wtEtaTV*EtaTVterm
            if constrain_isn:
                ISNterm = compute_isn_error(W)
                cost = cost + wtISN * ISNterm
            if constrain_coupling:
                Couplingterm = 0
                for el in coupling_constraints:
                    i, j, sgn = el
                    Couplingterm = Couplingterm + compute_coupling_error(
                        W, i, j, sgn)
                cost = cost + wtCoupling * Couplingterm
            if tv:
                TVterm = compute_tv_error(W)
                cost = cost + wtTV * TVterm

            if isinstance(Xterm, float):
                print('X:%f' % (wtX * Xterm))
                print('Y:%f' % (wtY * Yterm))
                print('Eta:%f' % (wtEta * Etaterm))
                print('Xi:%f' % (wtXi * Xiterm))
                print('Opto dYY:%f' % (wtOpto * wtdYY * dYYterm))
                print('Opto Eta:%f' % (wtOpto * wtEta12 * Eta12term))
                #print('TV:%f'%(wtEtaTV*EtaTVterm))
                print('TV:%f' % (wtTV * TVterm))
                if constrain_isn:
                    print('ISN:%f' % (wtISN * ISNterm))
                if constrain_coupling:
                    print('coupling:%f' % (wtCoupling * Couplingterm))

            #lbls = ['Yterm']
            #vars = [Yterm]
            lbls = ['cost']
            vars = [cost]
            for lbl, var in zip(lbls, vars):
                utils.print_labeled(lbl, var)
            return cost
    def minusLW(W1,W2,simulate=True,verbose=True):
        
        def compute_sq_error(a,b,wt):
            return np.sum(wt*(a-b)**2)
        
        def compute_kl_error(mu_data,pc_list,mu_model,fprimeval,wt):
            # how to model variability in X?
            kl = compute_kl_divergence(fprimeval,noise,mu_data,mu_model,pc_list)
            return kl #wt*kl
            # principled way would be to use 1/wt for noise term. Should add later.

        def compute_opto_error_nonlinear(fval,fval12,wt=None):
            if wt is None:
                wt = np.ones((2*nN,nQ*nS*nT))
            fval_both = np.concatenate((np.concatenate((fval,fval),axis=0)[:,np.newaxis,:],\
                    fval12[:,np.newaxis,:]),axis=1)
            this_fval12 = opto_transform1.preprocess(fval_both)
            dYY12 = this_fval12[:,1,:] - this_fval12[:,0,:]
            dYYterm = np.sum(wt[opto_mask]*(dYY12[opto_mask] - dYY[opto_mask])**2)
            return dYYterm

        def compute_opto_error_nonlinear_transform(fval,fval12,wt=None):
            if wt is None:
                wt = np.ones((2*nN,nQ*nS*nT))
            fval_both = np.concatenate((np.concatenate((fval,fval),axis=0)[:,np.newaxis,:],\
                    fval12[:,np.newaxis,:]),axis=1)
            this_fval12 = opto_transform1.preprocess(fval_both)[:,1,:]
            fval12target = np.concatenate((opto_transform1.transform(fval),opto_transform2.transform(fval)),axis=0)
            dYYterm = np.sum(wt[opto_mask]*(this_fval12[opto_mask] - fval12target[opto_mask])**2)
            return dYYterm

        def compute_coupling(W1,W2):
            #Wmx,Wmy,Wsx,Wsy,s02,K,kappa,T,XX,XXp,Eta,Xi,h1,h2 = parse_W(W)
            Wmx,Wmy,Wsx,Wsy,s02,K,kappa,T,h1,h2,bl,amp = parse_W1(W1)
            XX,XXp,Eta,Xi = parse_W2(W2)
            WWy = gen_Weight(Wmy,K,kappa,T)
            Phi = fprime_m(Eta,compute_var(Xi,s02))
            Phi = np.concatenate((Phi,Phi),axis=0)
            Phi1 = np.array([np.diag(phi) for phi in Phi])
            coupling = np.array([phi1 @ np.linalg.pinv(np.eye(nQ*nS*nT) - WWy @ phi1) for phi1 in Phi1])
            return coupling

        def compute_coupling_error(W1,W2,i,j,sgn=-1):
            # constrain coupling term i,j to have a specified sign, 
            # -1 for negative or +1 for positive
            coupling = compute_coupling(W1,W2)
            log_arg = sgn*coupling[:,i,j]
            cost = utils.minus_sum_log_slope(log_arg,big_val/nN)
            return cost

        #def compute_eta_tv(this_Eta):
        #    Etar = this_Eta.reshape((nsize,ncontrast,nQ*nS*nT))
        #    diff_size = np.sum(np.abs(np.diff(Etar,axis=0)))
        #    diff_contrast = np.sum(np.abs(np.diff(Etar,axis=1)))
        #    return diff_size + diff_contrast

        def compute_isn_error(W1,W2):
            #Wmx,Wmy,Wsx,Wsy,s02,K,kappa,T,XX,XXp,Eta,Xi,h1,h2 = parse_W(W)
            Wmx,Wmy,Wsx,Wsy,s02,K,kappa,T,h1,h2,bl,amp = parse_W1(W1)
            XX,XXp,Eta,Xi = parse_W2(W2)
            Phi = fprime_m(Eta,compute_var(Xi,s02))
            #print('min Eta: %f'%np.min(Eta[:,0]))
            #print('WEE: %f'%Wmy[0,0])
            #print('min phiE*WEE: %f'%np.min(Phi[:,0]*Wmy[0,0]))
            if K.size:
                k = K[0]
            else:
                k = 0
            if T.size:
                t = T[0]
            else:
                t = 0
            log_arg = Phi[:,0]*Wmy[0,0]*(1+k)*(1+t) - 1
            cost = utils.minus_sum_log_slope(log_arg,big_val/nN)
            #print('ISN cost: %f'%cost)
            return cost
        
        def compute_tv_error(W1,W2):
            # sq l2 norm for tv error
            #Wmx,Wmy,Wsx,Wsy,s02,K,kappa,T,XX,XXp,Eta,Xi,h1,h2 = parse_W(W)
            Wmx,Wmy,Wsx,Wsy,s02,K,kappa,T,h1,h2,bl,amp = parse_W1(W1)
            XX,XXp,Eta,Xi = parse_W2(W2)
            topo_var_list = [arr.reshape(topo_shape+(-1,)) for arr in \
                    [XX,XXp,Eta,Xi]]
            sqdiffy = [np.sum(np.abs(np.diff(top,axis=0))**2) for top in topo_var_list]
            sqdiffx = [np.sum(np.abs(np.diff(top,axis=1))**2) for top in topo_var_list]
            cost = np.sum(sqdiffy+sqdiffx)
            return cost

        def compute_smi_error(fval,fval12,halo_mult=1,chrimson_mult=1):
            fval = compute_f_(Eta,Xi,s02)
            ipc = 0
            def compute_dsmi(fval):
                fpc = fval[:,ipc].reshape(topo_shape)
                smi = fpc[-1,:]/np.max(fpc,0)
                dsmi = smi[1] - smi[5]
                return dsmi
            dsmis = [compute_dsmi(f) for f in [fval,fval12[:nN],fval12[nN:]]]
            smi_halo_error = halo_mult*(dsmis[1] - dsmis[0])**2
            smi_chrimson_error = chrimson_mult*utils.minus_sum_log_slope(dsmis[2] - dsmis[0],big_val)
            smi_baseline_error = 1*utils.minus_sum_log_slope(dsmis[0],big_val)
            return smi_halo_error,smi_chrimson_error,smi_baseline_error

        #Wmx,Wmy,Wsx,Wsy,s02,K,kappa,T,XX,XXp,Eta,Xi,h1,h2 = parse_W(W)
        Wmx,Wmy,Wsx,Wsy,s02,K,kappa,T,h1,h2,bl,amp = parse_W1(W1)
        XX,XXp,Eta,Xi = parse_W2(W2)

        #utils.print_labeled('T',T)
        #utils.print_labeled('K',K)
        #utils.print_labeled('Wmy',Wmy)
        
        perturbation = perturbation_size*np.random.randn(*Eta.shape)
        
#         fval,fprimeval = compute_f_fprime_t_(W1,W2,perturbation) # Eta the mean input per cell, Xi the stdev. input per cell, s02 the baseline variability in input
        #print('simulate: '+str(simulate))
        if simulate:
            fval,fprimeval = compute_f_fprime_t_avg_(W1,W2,perturbation) # Eta the mean input per cell, Xi the stdev. input per cell, s02 the baseline variability in input
        else:
            fval,fprimeval = compute_f_fprime_(W1,W2) # Eta the mean input per cell, Xi the stdev. input per cell, s02 the baseline variability in input
        fval12,fprimeval12 = compute_f_fprime_t_avg_12_(W1,W2,perturbation) # Eta the mean input per cell, Xi the stdev. input per cell, s02 the baseline variability in input
        #utils.print_labeled('fval',fval)

        bltile = np.tile(bl,nS*nT)[np.newaxis,:]
        
        Xterm = compute_kl_error(XXhat,Xpc_list,XX,XXp,wtStim*wtInp) # XX the modeled input layer (L4)
        Yterm = compute_kl_error(YYhat,Ypc_list,amp*fval+bltile,amp*fprimeval,wtStim*wtCell) # fval the modeled output layer (L2/3)

        Etaterm = compute_sq_error(Eta,u_fn(XX,fval,Wmx,Wmy,K,kappa,T),wtStim*wtCell) # magnitude of fudge factor in mean input
        Xiterm = compute_sq_error(Xi,u_fn(XX,fval,Wsx,Wsy,K,kappa,T),wtStim*wtCell) # magnitude of fudge factor in input variability
        # returns value float
        #Optoterm = compute_opto_error_nonlinear(W) #testing out 8/20/20
        opto_wt = np.concatenate([wtStimOpto*wtCellOpto*w for w in wtDirOpto],axis=0)
        if wtSMI != 0:
            SMIhaloterm,SMIchrimsonterm,SMIbaselineterm = compute_smi_error(fval,fval12,halo_mult=1,chrimson_mult=1)
        else:
            SMIhaloterm,SMIchrimsonterm,SMIbaselineterm = 0,0,0
        if wtdYY != 0:
            if use_opto_transforms:
                dYYterm = compute_opto_error_nonlinear_transform(amp*fval+bltile,amp*fval12+bltile,opto_wt)
            else:
                dYYterm = compute_opto_error_nonlinear(amp*fval+bltile,amp*fval12+bltile,opto_wt)
            Optoterm = wtdYY*dYYterm
        else:
            Optoterm = 0
        cost = wtX*Xterm + wtY*Yterm + wtEta*Etaterm + wtXi*Xiterm + wtOpto*Optoterm + wtSMIhalo*SMIhaloterm + wtSMIchrimson*SMIchrimsonterm + wtSMI*SMIbaselineterm# + wtEtaTV*EtaTVterm 
        if constrain_isn:
            ISNterm = compute_isn_error(W1,W2)
            cost = cost + wtISN*ISNterm
        if constrain_coupling:
            Couplingterm = 0
            for el in coupling_constraints:
                i,j,sgn = el
                Couplingterm = Couplingterm + compute_coupling_error(W1,W2,i,j,sgn)
            cost = cost + wtCoupling*Couplingterm
        if tv:
            TVterm = compute_tv_error(W1,W2)
            cost = cost + wtTV*TVterm

        #print('Yterm as float: '+str(float(Yterm)))
        #print('Yterm as float: '+str(Yterm.astype('float')))
            
        if isinstance(Yterm,float) and verbose:
            print('X:%f'%(wtX*Xterm))
            print('Y:%f'%(wtY*Yterm.astype('float')))
            print('Eta:%f'%(wtEta*Etaterm))
            print('Xi:%f'%(wtXi*Xiterm))
            print('Opto dYY:%f'%(wtOpto*wtdYY*dYYterm))
            #print('Opto Eta:%f'%(wtOpto*wtEta12*Eta12term))
            #print('TV:%f'%(wtEtaTV*EtaTVterm))
            print('TV:%f'%(wtTV*TVterm))
            print('SMI halo:%f'%(wtSMIhalo*SMIhaloterm))
            print('SMI chrimson:%f'%(wtSMIchrimson*SMIchrimsonterm))
            print('SMI baseline:%f'%(wtSMI*SMIbaselineterm))
            if constrain_isn:
                print('ISN:%f'%(wtISN*ISNterm))
            if constrain_coupling:
                print('coupling:%f'%(wtCoupling*Couplingterm))

        #lbls = ['Yterm']
        #vars = [Yterm]
        lbls = ['cost']
        vars = [cost]
        if verbose:
            for lbl,var in zip(lbls,vars):
                utils.print_labeled(lbl,var)
        return cost