Exemplo n.º 1
0
    def __init__(self, T, M, ln_k, x0, algo_options):
        self.T = T
        self.M = M
        self.ln_k = ln_k
        self.x0 = x0
        self.s = np.ones_like(x0)

        self.bisymlog = Bisymlog(C=bisymlog_C, scaling_factor=2.0, base=np.exp(1))

        self.penalty_vars = {'lambda': np.array([1,1,1,1]), 'rho': 1.0, # rho is penalty parameter rho > 0
                             'mu': 0.1}  # mu > 0

        self.Fcent_min = 1E-6
        self.Tmin = 273   # 100
        self.Tmax = 6000  # 20000

        self.algo = algo_options

        self.loss_alpha = self.algo['loss_fcn_param'][0]   # warning unknown how well this functions outside of alpha=2, C=1
        self.loss_scale = self.algo['loss_fcn_param'][1]
        
        # change all E-30 values to np.nan so only T2 is optimized
        if (self.x0[-3:-1] < 10).all() or np.isnan(self.x0).any():
            self.x0[-4:-1] = [1.0, 1.0E-30, 1.0E-30]
            self.Fcent_idx = [9]
        else:
            self.Fcent_idx = [6,7,8,9]

        if all(self.algo['is_P_limit']):
            self.alter_idx = self.Fcent_idx
        elif self.algo['is_P_limit'][0]:
            self.alter_idx = [3,4,5, *self.Fcent_idx]
        elif self.algo['is_P_limit'][1]:
            self.alter_idx = [0,1,2, *self.Fcent_idx]
        else:
            self.alter_idx = [0,1,2,3,4,5, *self.Fcent_idx]

        self.x = deepcopy(self.x0)
Exemplo n.º 2
0
    def __init__(self, parent, widget, mpl_layout):
        super().__init__(parent)
        self.parent = parent

        self.widget = widget
        self.mpl_layout = mpl_layout
        self.fig = mplfigure.Figure()
        mpl.scale.register_scale(AbsoluteLogScale)
        mpl.scale.register_scale(BiSymmetricLogScale)

        # set layout to be tight
        self.fig.tight_layout()

        # Set plot variables
        self.x_zoom_constraint = False
        self.y_zoom_constraint = False

        self.create_canvas()
        self.NavigationToolbar(self.canvas, self.widget, coordinates=True)

        # AutoScale
        self.autoScale = [True, True]

        # Bisymlog
        self.bisymlog = Bisymlog(C=None,
                                 scaling_factor=bisymlog_scaling_factor)

        # Connect Signals
        self._draw_event_signal = self.canvas.mpl_connect(
            'draw_event', self._draw_event)
        self.canvas.mpl_connect('button_press_event',
                                lambda event: self.click(event))
        self.canvas.mpl_connect('key_press_event',
                                lambda event: self.key_press(event))
        # self.canvas.mpl_connect('key_release_event', lambda event: self.key_release(event))

        self._draw_event()
Exemplo n.º 3
0
    class InvertedBiSymLogTransform(mpl.transforms.Transform):
        input_dims = 1
        output_dims = 1
        is_separable = True

        def __init__(self, C, base=10):
            mpl.transforms.Transform.__init__(self)
            self.base = base
            self.C = C
            self.bisymlog = Bisymlog(C=C, scaling_factor=2.0, base=base)

        def transform_non_affine(self, x):
            return self.bisymlog.invTransform(x)

        def inverted(self):
            return BiSymmetricLogScale.BiSymLogTransform(self.C)
Exemplo n.º 4
0
 def __init__(self, C, base=10):
     mpl.transforms.Transform.__init__(self)
     self.base = base
     self.C = C
     self.bisymlog = Bisymlog(C=C, scaling_factor=2.0, base=base)
Exemplo n.º 5
0
class falloff_parameters:   # based on ln_Fcent
    def __init__(self, T, M, ln_k, x0, algo_options):
        self.T = T
        self.M = M
        self.ln_k = ln_k
        self.x0 = x0
        self.s = np.ones_like(x0)

        self.bisymlog = Bisymlog(C=bisymlog_C, scaling_factor=2.0, base=np.exp(1))

        self.penalty_vars = {'lambda': np.array([1,1,1,1]), 'rho': 1.0, # rho is penalty parameter rho > 0
                             'mu': 0.1}  # mu > 0

        self.Fcent_min = 1E-6
        self.Tmin = 273   # 100
        self.Tmax = 6000  # 20000

        self.algo = algo_options

        self.loss_alpha = self.algo['loss_fcn_param'][0]   # warning unknown how well this functions outside of alpha=2, C=1
        self.loss_scale = self.algo['loss_fcn_param'][1]
        
        # change all E-30 values to np.nan so only T2 is optimized
        if (self.x0[-3:-1] < 10).all() or np.isnan(self.x0).any():
            self.x0[-4:-1] = [1.0, 1.0E-30, 1.0E-30]
            self.Fcent_idx = [9]
        else:
            self.Fcent_idx = [6,7,8,9]

        if all(self.algo['is_P_limit']):
            self.alter_idx = self.Fcent_idx
        elif self.algo['is_P_limit'][0]:
            self.alter_idx = [3,4,5, *self.Fcent_idx]
        elif self.algo['is_P_limit'][1]:
            self.alter_idx = [0,1,2, *self.Fcent_idx]
        else:
            self.alter_idx = [0,1,2,3,4,5, *self.Fcent_idx]

        self.x = deepcopy(self.x0)

    def x_bnds(self, x0):
        bnds = []
        for n, coef in enumerate(['A', 'T3', 'T1', 'T2']):
            if x0[n] < 0:
                bnds.append(troe_all_bnds[coef]['-'])
            elif x0[n] > 0:
                bnds.append(troe_all_bnds[coef]['+'])
            else:   # if doesn't match either of the above then put nan as bounds
                bnds.append([np.nan, np.nan])

        return np.array(bnds).T

    def fit(self):
        T = self.T

        self.p0 = self.x0
        self.p0[-3:] = self.convert_Fcent(self.p0[-3:], 'base2opt')

        #s = np.array([4184, 1.0, 1E-2, 4184, 1.0, 1E-2, *(np.ones((1,4)).flatten()*1E-1)])

        p_bnds = set_arrhenius_bnds(self.p0[0:3], default_arrhenius_coefNames)
        p_bnds = np.concatenate((p_bnds, set_arrhenius_bnds(self.p0[3:6], default_arrhenius_coefNames)), axis=1)
        p_bnds[:,1] = np.log(p_bnds[:,1])   # setting bnds to ln(A), need to do this better
        p_bnds[:,4] = np.log(p_bnds[:,4])   # setting bnds to ln(A), need to do this better

        Fcent_bnds = self.x_bnds(self.p0[-4:])
        Fcent_bnds[-3:] = self.convert_Fcent(Fcent_bnds[-3:])

        self.p_bnds = np.concatenate((p_bnds, Fcent_bnds), axis=1)

        if len(self.p_bnds) > 0:
            self.p0 = np.clip(self.p0, self.p_bnds[0, :], self.p_bnds[1, :])

        self.p0 = self.p0[self.alter_idx]
        self.p_bnds = self.p_bnds[:,self.alter_idx]
        self.s = np.ones_like(self.p0)

        if self.algo['algorithm'] == 'scipy_curve_fit':
            with warnings.catch_warnings():
                warnings.simplefilter('ignore', OptimizeWarning)
                x_fit, _ = curve_fit(self.ln_Troe, T, self.ln_k, p0=self.p0, method='trf', bounds=self.p_bnds, # dogbox
                                        #jac=fit_func_jac, x_scale='jac', max_nfev=len(self.p0)*1000)
                                        jac='2-point', x_scale='jac', max_nfev=len(self.p0)*1000, loss='huber')

        #print('scipy:', x_fit)
        #cmp = np.array([T, Fcent, np.exp(fit_func(T, *x_fit))]).T
        #for entry in cmp:
        #    print(*entry)
        #print('')
        #scipy_fit = np.exp(self.function(T, *x_fit))

        else: # maybe try pygmo with cstrs_self_adaptive or unconstrain or decompose
            p0_opt = np.zeros_like(self.p0)
            self.s = self.calc_s(p0_opt)
            bnds = (self.p_bnds-self.p0)/self.s
            

            opt = nlopt.opt(nlopt.AUGLAG, len(self.p0))
            #opt = nlopt.opt(self.algo['algorithm'], len(self.p0))

            opt.set_min_objective(self.objective)
            opt.add_inequality_constraint(self.Fcent_constraint, 0.0)
            opt.add_inequality_constraint(self.Arrhenius_constraint, 1E-8)
            opt.set_maxeval(self.algo['max_eval'])
            #opt.set_maxtime(10)

            opt.set_xtol_rel(self.algo['xtol_rel'])
            opt.set_ftol_rel(self.algo['ftol_rel'])

            opt.set_lower_bounds(bnds[0])
            opt.set_upper_bounds(bnds[1])

            opt.set_initial_step(self.algo['initial_step'])
            #opt.set_population(int(np.rint(10*(len(idx)+1)*10)))
            
            sub_opt = nlopt.opt(self.algo['algorithm'], len(self.p0))
            sub_opt.set_initial_step(self.algo['initial_step'])
            sub_opt.set_xtol_rel(self.algo['xtol_rel'])
            sub_opt.set_ftol_rel(self.algo['ftol_rel'])
            opt.set_local_optimizer(sub_opt)

            x_fit = opt.optimize(p0_opt) # optimize!
            #print('Fcent_constraint: ', self.Fcent_constraint(x_fit))
            #print('Arrhe_constraint: ', self.Arrhenius_constraint(x_fit))

            x_fit = self.set_x_from_opt(x_fit)

        #print('nlopt:', x_fit)
        #cmp = np.array([T, Fcent, self.function(T, *x_fit)]).T
        ##cmp = np.array([T, Fcent, scipy_fit, np.exp(self.function(T, *x_fit))]).T
        #for entry in cmp:
        #    print(*entry)
        #print('')

        # change ln_A to A
        x_fit[1] = np.exp(x_fit[1])
        x_fit[4] = np.exp(x_fit[4])

        res = {'x': x_fit, 'fval': opt.last_optimum_value(), 'nfev': opt.get_numevals()}

        return res

    def set_x_from_opt(self, x):
        self.x[self.alter_idx] = x*self.s + self.p0
        x = np.array(self.x)
        x[-3:] = self.convert_Fcent(x[-3:], 'opt2base')

        for i in [7, 8]:
            if x[i] >= 0:
                if x[i] < 10:
                    x[i] = 1E-30
                elif x[i] > 1E8:
                    x[i] = 1E30

            elif np.isnan(x[i]):    # I don't actually know why it's nan sometimes
                x[i] = 1E-30

        return x

    def convert_Fcent(self, x, conv_type='base2opt'):
        #x = x*self.s + self.p0
        y = np.array(x)
        C = bisymlog_C

        flatten = False
        if y.ndim == 1:
            y = y[np.newaxis, :]
            flatten = True

        if conv_type == 'base2opt': # y = [A, T3, T1, T2]
            y = self.bisymlog.transform(y)

        else:
            y = self.bisymlog.invTransform(y)

            #A = np.log(y[0])
            #T3, T1 = 1000/y[1], 1000/y[2]
            #T2 = y[3]*100

        if flatten: # if it's 1d it's the guess
            y = y.flatten()

        else:       # if it 2d it's the bnds, they need to be sorted
            y = np.sort(y, axis=0)

        return y

    def jacobian(self, T, *x):  # defunct
        [A, B, C, D] = x
        [A, T3, T1, T2] = self.convert_Fcent(x, 'opt2base') # A, B, C, D = x
        bC = bisymlog_C

        jac = []
        jac.append((np.exp(-T/T1) - np.exp(-T/T3)))                   # dFcent/dA
        jac.append(bC*np.exp(np.abs(B))*(1-A)*T/T3**2*np.exp(-T/T3))  # dFcent/dB
        jac.append(bC*np.exp(np.abs(C))*A*T/T1**2*np.exp(-T/T1))      # dFcent/dC
        jac.append(-bC*np.exp(np.abs(D))/T*np.exp(-T2/T))             # dFcent/dD

        jac = np.vstack(jac).T

        return jac

    def objective(self, x_fit, grad=np.array([]), obj_type='obj_sum', aug_lagrangian=False):
        x = self.set_x_from_opt(x_fit)
        T = self.T
        M = self.M

        resid = ln_Troe(T, M, *x) - self.ln_k
        #resid = self.ln_Troe(T, *x) - self.ln_k
        if obj_type == 'obj_sum':              
            obj_val = penalized_loss_fcn(resid, a=self.loss_alpha, c=self.loss_scale).sum()
        elif obj_type == 'obj':
            obj_val = penalized_loss_fcn(resid, a=self.loss_alpha, c=self.loss_scale)
        elif obj_type == 'resid':
            obj_val = resid

        if aug_lagrangian: # https://arxiv.org/pdf/2106.15044.pdf, https://www.him.uni-bonn.de/fileadmin/him/Section6_HIM_v1.pdf
            lamb = self.penalty_vars['lambda']
            mu = self.penalty_vars['mu']
            rho = self.penalty_vars['rho']

            Ea_0, ln_A_0, n_0 = x[:3]
            Ea_inf, ln_A_inf, n_inf = x[3:6]
            Fcent_coeffs = x[-4:] # A, T3, T1, T2

            con = []

            # Arrhenius constraints
            T_max = T[0,-1]

            ln_k_0 = ln_A_0 + n_0*np.log(T_max) - Ea_0/(Ru*T_max)
            ln_k_inf = ln_A_inf + n_inf*np.log(T_max) - Ea_inf/(Ru*T_max)
            con.append(np.max([0, ln_k_max - ln_k_0]))        # ln_k_0 <= ln_k_max
            con.append(np.max([0, ln_k_max - ln_k_inf]))      # ln_k_0 <= ln_k_max

            # Fcent constraints
            T_con = np.array([self.Tmin, *T[0,:], self.Tmax])
            Fcent = Fcent_calc(T_con, *Fcent_coeffs)
            con.append(np.max([0, np.min(Fcent - self.Fcent_min)]))   # Fcent >= Fcent_min
            con.append(np.max([0, np.min(1.0 - Fcent)]))              # Fcent <= 1.0

            con = np.array(con)

            z = 0.5/rho*(((lamb - rho*con)**2 + 4*rho*mu)**0.5 - (lamb - rho*con))
            penalty = 0.0
            for zi in z:
                if zi != 0.0 and zi > min_pos_system_value:
                    penalty += np.log(zi)

            penalty *= mu

            lamb = self.penalty_vars['lambda'] = lamb + rho*(z-con)

            err_norm = np.linalg.norm((z-con))
            if err_norm > 0.95*mu:
                rho_new = 2*rho
                mu_new = mu
            else:
                rho_new = np.max([rho, np.linalg.norm(lamb)])
                mu_new = 0.1*mu

            if rho_new > max_pos_system_value:
                self.penalty_vars['rho'] = max_pos_system_value
            else:
                self.penalty_vars['rho'] = rho_new

            if mu_new < min_pos_system_value:
                self.penalty_vars['mu'] = min_pos_system_value
            else:
                self.penalty_vars['mu'] = mu_new

            obj_val -= penalty

        #s[:] = np.abs(np.sum(loss*fit_func_jac(T, *x).T, axis=1))
        if grad.size > 0:
            grad[:] = self.objective_gradient(x, resid, numerical_gradient=True)
        #else:
        #    grad = self.objective_gradient(x, resid)

        #self.s = self.calc_s(x_fit, grad)

        #self.opt.set_lower_bounds((self.p_bnds[0] - self.p0)/self.s)
        #self.opt.set_upper_bounds((self.p_bnds[1] - self.p0)/self.s)

        return obj_val
    
    def Fcent_constraint(self, x_fit, grad=np.array([])):
        def f_fp(T, A, T3, T1, T2, fprime=False, fprime2=False): # dFcent_dT 
            f = T2/T**2*np.exp(-T2/T) - (1-A)/T3*np.exp(-T/T3) - A/T1*np.exp(-T/T1)

            if not fprime and not fprime2:
                return f
            elif fprime and not fprime2:
                fp = T2*(T2 - 2*T)/T**4*np.exp(-T2/T) + (1-A)/T3**2*np.exp(-T/T3) +A/T1**2*np.exp(-T/T1)
                return f, fp

        x = self.set_x_from_opt(x_fit)
        [A, T3, T1, T2] = x[-4:]
        Tmin = self.Tmin
        Tmax = self.Tmax

        try:
            T_deriv_eq_0 = root_scalar(lambda T: f_fp(A, T3, T1, T2), 
                                        x0=(Tmax+Tmin)/4, x1=3*(Tmax+Tmin)/4, method='secant')
            T = np.array([Tmin, T_deriv_eq_0, Tmax])
        except:
            T = np.array([Tmin, Tmax])

        if len(T) == 3:
            print(T)

        Fcent = Fcent_calc(T, A, T3, T1, T2)   #TODO: OVERFLOW WARNING HERE
        min_con = np.max(self.Fcent_min - Fcent)
        max_con = np.max(Fcent - 1.0)
        con = np.max([max_con, min_con])*1E8

        if grad.size > 0:
            grad[:] = self.constraint_gradient(x, numerical_gradient=self.Fcent_constraint)

        return con

    def Arrhenius_constraint(self, x_fit, grad=np.array([])):
        x = self.set_x_from_opt(x_fit)

        T = self.T
        T_max = T[-1]

        Ea_0, ln_A_0, n_0 = x[:3]
        Ea_inf, ln_A_inf, n_inf = x[3:6]

        ln_k_0 = ln_A_0 + n_0*np.log(T_max) - Ea_0/(Ru*T_max)
        ln_k_inf = ln_A_inf + n_inf*np.log(T_max) - Ea_inf/(Ru*T_max)

        ln_k_limit_max = np.max([ln_k_0, ln_k_inf])

        con = ln_k_limit_max - ln_k_max

        if grad.size > 0:
            grad[:] = self.constraint_gradient(x, numerical_gradient=self.Arrhenius_constraint)

        return con

    def constraints(self, x_fit, grad=np.array([])):
        x = self.set_x_from_opt(x_fit)
        T = self.T

        Ea_0, ln_A_0, n_0 = x[:3]
        Ea_inf, ln_A_inf, n_inf = x[3:6]
        Fcent_coeffs = x[-4:] # A, T3, T1, T2

        con = []

        # Arrhenius constraints
        T_max = T[0,-1]

        ln_k_0 = ln_arrhenius_k(T_max, Ea_0, ln_A_0, n_0)
        ln_k_inf = ln_arrhenius_k(T_max, Ea_inf, ln_A_inf, n_inf)

        con.append(np.max([0, ln_k_max - ln_k_0]))        # ln_k_0 <= ln_k_max
        con.append(np.max([0, ln_k_max - ln_k_inf]))      # ln_k_0 <= ln_k_max

        # Fcent constraints
        T_con = np.array([self.Tmin, *T[0,:], self.Tmax])
        Fcent = Fcent_calc(T_con, *Fcent_coeffs)
        con.append(np.max([0, np.min(Fcent - self.Fcent_min)]))   # Fcent >= Fcent_min
        con.append(np.max([0, np.min(1.0 - Fcent)]))              # Fcent <= 1.0

        con = np.array(con)

    def objective_gradient(self, x, resid=[], numerical_gradient=False):
        if numerical_gradient:
        #x = (x - self.p0)/self.s
            grad = approx_fprime(x, self.objective, 1E-10)
            
        else:
            if len(resid) == 0:
                resid = self.objective(x, obj_type='resid')

            x = x*self.s + self.p0
            T = self.T
            jac = self.jacobian(T, *x)
            if np.isfinite(jac).all():
                with np.errstate(all='ignore'):
                    grad = np.sum(jac.T*resid, axis=1)*self.s
                    grad[grad == np.inf] = max_pos_system_value
            else:
                grad = np.ones_like(self.p0)*max_pos_system_value

        return grad

    def calc_s(self, x, grad=[]):
        if len(grad) == 0:
            grad = self.objective_gradient(x, numerical_gradient=True)

        y = np.abs(grad)
        if (y < min_pos_system_value).all():
            y = np.ones_like(y)*1E-14
        else:
            y[y < min_pos_system_value] = 10**(OoM(np.min(y[y>=min_pos_system_value])) - 1)  # TODO: MAKE THIS BETTER running into problem when s is zero, this is a janky workaround
        
        s = 1/y
        #s = s/np.min(s)
        #s = s/np.max(s)

        return s

    def Fcent_constraint(self, x_fit, grad=np.array([])):
        def f_fp(T, A, T3, T1, T2, fprime=False, fprime2=False): # dFcent_dT 
            f = T2/T**2*np.exp(-T2/T) - (1-A)/T3*np.exp(-T/T3) - A/T1*np.exp(-T/T1)

            if not fprime and not fprime2:
                return f
            elif fprime and not fprime2:
                fp = T2*(T2 - 2*T)/T**4*np.exp(-T2/T) + (1-A)/T3**2*np.exp(-T/T3) +A/T1**2*np.exp(-T/T1)
                return f, fp

        x = self.set_x_from_opt(x_fit)
        [A, T3, T1, T2] = x[-4:]
        Tmin = self.Tmin
        Tmax = self.Tmax

        try:
            T_deriv_eq_0 = root_scalar(lambda T: f_fp(A, T3, T1, T2), 
                                        x0=(Tmax+Tmin)/4, x1=3*(Tmax+Tmin)/4, method='secant')
            T = np.array([Tmin, T_deriv_eq_0, Tmax])
        except:
            T = np.array([Tmin, Tmax])

        if len(T) == 3:
            print(T)

        Fcent = Fcent_calc(T, A, T3, T1, T2)   #TODO: OVERFLOW WARNING HERE
        min_con = np.max(self.Fcent_min - Fcent)
        max_con = np.max(Fcent - 1.0)
        con = np.max([max_con, min_con])*1E8

        if grad.size > 0:
            grad[:] = self.constraint_gradient(x, numerical_gradient=self.Fcent_constraint)

        return con

    def Arrhenius_constraint(self, x_fit, grad=np.array([])):
        x = self.set_x_from_opt(x_fit)

        T = self.T
        T_max = T[-1]

        Ea_0, ln_A_0, n_0 = x[:3]
        Ea_inf, ln_A_inf, n_inf = x[3:6]

        ln_k_0 = ln_A_0 + n_0*np.log(T_max) - Ea_0/(Ru*T_max)
        ln_k_inf = ln_A_inf + n_inf*np.log(T_max) - Ea_inf/(Ru*T_max)

        ln_k_limit_max = np.max([ln_k_0, ln_k_inf])

        con = ln_k_limit_max - ln_k_max

        if grad.size > 0:
            grad[:] = self.constraint_gradient(x, numerical_gradient=self.Arrhenius_constraint)

        return con

    def constraint_gradient(self, x, const_eval=[], numerical_gradient=None):
        if numerical_gradient is not None:
            grad = approx_fprime(x, numerical_gradient, 1E-10)
            
        else:   # I've not calculated the derivatives wrt coefficients for analytical
            if len(resid) == 0:
                const_eval = self.objective(x)

            T = self.T
            jac = self.jacobian(T, *x)
            if np.isfinite(jac).all():
                with np.errstate(all='ignore'):
                    grad = np.sum(jac.T*const_eval, axis=1)*self.s
                    grad[grad == np.inf] = max_pos_system_value
            else:
                grad = np.ones_like(self.x0)*max_pos_system_value

        return grad
Exemplo n.º 6
0
    def resid_func(t_offset,
                   t_adjust,
                   t_sim,
                   obs_sim,
                   t_exp,
                   obs_exp,
                   weights,
                   obs_bounds=[],
                   loss_alpha=2,
                   loss_c=1,
                   loss_penalty=True,
                   scale='Linear',
                   bisymlog_scaling_factor=1.0,
                   DoF=1,
                   opt_type='Residual',
                   verbose=False):
        def calc_exp_bounds(t_sim, t_exp):
            t_bounds = [max([t_sim[0],
                             t_exp[0]])]  # Largest initial time in SIM and Exp
            t_bounds.append(min([t_sim[-1], t_exp[-1]
                                 ]))  # Smallest final time in SIM and Exp
            # Values within t_bounds
            exp_bounds = np.where(
                np.logical_and((t_exp >= t_bounds[0]),
                               (t_exp <= t_bounds[1])))[0]

            return exp_bounds

        # Compare SIM Density Grad vs. Experimental
        t_sim_shifted = t_sim + t_offset + t_adjust
        exp_bounds = calc_exp_bounds(t_sim_shifted, t_exp)
        t_exp, obs_exp, weights = t_exp[exp_bounds], obs_exp[
            exp_bounds], weights[exp_bounds]
        if opt_type == 'Bayesian':
            obs_bounds = obs_bounds[exp_bounds]

        f_interp = CubicSpline(t_sim.flatten(), obs_sim.flatten())
        t_exp_shifted = t_exp - t_offset - t_adjust
        obs_sim_interp = f_interp(t_exp_shifted)

        if scale == 'Linear':
            resid = np.subtract(obs_exp, obs_sim_interp)

        elif scale == 'Log':
            ind = np.argwhere(((obs_exp != 0.0) & (obs_sim_interp != 0.0)))
            exp_bounds = exp_bounds[ind]
            weights = weights[ind].flatten()

            m = np.ones_like(obs_exp[ind])
            i_g = obs_exp[ind] >= obs_sim_interp[ind]
            i_l = obs_exp[ind] < obs_sim_interp[ind]
            m[i_g] = np.divide(obs_exp[ind][i_g], obs_sim_interp[ind][i_g])
            m[i_l] = np.divide(obs_sim_interp[ind][i_l], obs_exp[ind][i_l])
            resid = np.log10(np.abs(m)).flatten()
            if verbose and opt_type == 'Bayesian':
                obs_exp = np.log10(np.abs(
                    obs_exp[ind])).squeeze()  # squeeze to remove extra dim
                obs_sim_interp = np.log10(np.abs(
                    obs_sim_interp[ind])).squeeze()
                obs_bounds = np.log10(np.abs(obs_bounds[ind])).squeeze()

        elif scale == 'Bisymlog':
            bisymlog = Bisymlog(C=None, scaling_factor=bisymlog_scaling_factor)
            bisymlog.set_C_heuristically(obs_exp)
            obs_exp_bisymlog = bisymlog.transform(obs_exp)
            obs_sim_interp_bisymlog = bisymlog.transform(obs_sim_interp)
            resid = np.subtract(obs_exp_bisymlog, obs_sim_interp_bisymlog)
            if verbose and opt_type == 'Bayesian':
                obs_exp = obs_exp_bisymlog
                obs_sim_interp = obs_sim_interp_bisymlog
                obs_bounds = bisymlog.transform(
                    obs_bounds)  # THIS NEEDS TO BE CHECKED

        resid_outlier = outlier(resid, c=loss_c, weights=weights)
        loss = penalized_loss_fcn(resid,
                                  a=loss_alpha,
                                  c=resid_outlier,
                                  use_penalty=loss_penalty)

        #loss = rescale_loss_fcn(np.abs(resid), loss, resid_outlier, weights)

        loss_sqr = (loss**2) * weights
        wgt_sum = weights.sum()
        N = wgt_sum - DoF
        if N <= 0:
            N = wgt_sum
        stderr_sqr = loss_sqr.sum() * wgt_sum / N
        chi_sqr = loss_sqr / stderr_sqr
        std_resid = chi_sqr**(0.5)
        #loss_scalar = (chi_sqr*weights).sum()
        loss_scalar = std_resid.sum()
        #loss_scalar = np.average(std_resid, weights=weights)
        #loss_scalar = weighted_quantile(std_resid, 0.5, weights=weights)    # median value

        if verbose:
            output = {
                'chi_sqr': chi_sqr,
                'resid': resid,
                'resid_outlier': resid_outlier,
                'loss': loss_scalar,
                'weights': weights,
                'obs_sim_interp': obs_sim_interp,
                'obs_exp': obs_exp
            }

            if opt_type == 'Bayesian':  # need to calculate aggregate weights to reduce outliers in bayesian
                SSE = penalized_loss_fcn(resid / resid_outlier,
                                         use_penalty=False)
                #SSE = penalized_loss_fcn(resid)
                #SSE = rescale_loss_fcn(np.abs(resid), SSE, resid_outlier, weights)
                loss_weights = loss / SSE  # comparison is between selected loss fcn and SSE (L2 loss)
                output['aggregate_weights'] = weights * loss_weights
                output['obs_bounds'] = obs_bounds

            return output

        else:  # needs to return single value for optimization
            return loss_scalar
Exemplo n.º 7
0
class Base_Plot(QtCore.QObject):
    def __init__(self, parent, widget, mpl_layout):
        super().__init__(parent)
        self.parent = parent

        self.widget = widget
        self.mpl_layout = mpl_layout
        self.fig = mplfigure.Figure()
        mpl.scale.register_scale(AbsoluteLogScale)
        mpl.scale.register_scale(BiSymmetricLogScale)

        # set layout to be tight
        self.fig.tight_layout()

        # Set plot variables
        self.x_zoom_constraint = False
        self.y_zoom_constraint = False

        self.create_canvas()
        self.NavigationToolbar(self.canvas, self.widget, coordinates=True)

        # AutoScale
        self.autoScale = [True, True]

        # Bisymlog
        self.bisymlog = Bisymlog(C=None,
                                 scaling_factor=bisymlog_scaling_factor)

        # Connect Signals
        self._draw_event_signal = self.canvas.mpl_connect(
            'draw_event', self._draw_event)
        self.canvas.mpl_connect('button_press_event',
                                lambda event: self.click(event))
        self.canvas.mpl_connect('key_press_event',
                                lambda event: self.key_press(event))
        # self.canvas.mpl_connect('key_release_event', lambda event: self.key_release(event))

        self._draw_event()

    def create_canvas(self):
        self.canvas = FigureCanvas(self.fig)
        self.mpl_layout.addWidget(self.canvas)
        self.canvas.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.canvas.draw()

        # Set scales
        scales = {'linear': True, 'log': 0, 'abslog': 0, 'bisymlog': 0}
        for ax in self.ax:
            ax.scale = {'x': scales, 'y': deepcopy(scales)}
            ax.ticklabel_format(scilimits=(-3, 4), useMathText=True)
            ax.animateAxisLabels = False

        # Get background
        self.background_data = self.canvas.copy_from_bbox(ax.bbox)

    def _find_calling_axes(self, event):
        for axes in self.ax:  # identify calling axis
            if axes == event or (hasattr(event, 'inaxes')
                                 and event.inaxes == axes):
                return axes

    def set_xlim(self, axes, x):
        if not self.autoScale[0]: return  # obey autoscale right click option
        if np.count_nonzero(np.isfinite(x)) < 2:
            return  # check size of vector, return if < 2

        if axes.get_xscale() in ['linear']:
            # range = np.abs(np.max(x) - np.min(x))
            # min = np.min(x) - range*0.05
            # if min < 0:
            # min = 0
            # xlim = [min, np.max(x) + range*0.05]
            xlim = [np.min(x), np.max(x)]
        if 'log' in axes.get_xscale():
            abs_x = np.abs(x)
            abs_x = abs_x[np.nonzero(abs_x)]  # exclude 0's

            if axes.get_xscale() in ['log', 'abslog', 'bisymlog']:
                min_data = np.ceil(np.log10(np.min(abs_x)))
                max_data = np.floor(np.log10(np.max(abs_x)))

                xlim = [10**(min_data - 1), 10**(max_data + 1)]

        if np.isnan(xlim).any() or np.isinf(xlim).any() or xlim[0] == xlim[1]:
            pass
        elif xlim != axes.get_xlim():  # if xlim changes
            axes.set_xlim(xlim)

    def set_ylim(self, axes, y):
        if not self.autoScale[1]: return  # obey autoscale right click option
        if np.count_nonzero(np.isfinite(y)) < 2:
            return  # check size of vector, return if < 2

        min_data = np.array(y)[np.isfinite(y)].min()
        max_data = np.array(y)[np.isfinite(y)].max()

        if min_data == max_data:
            min_data -= 10**-1
            max_data += 10**-1

        if axes.get_yscale() == 'linear':
            range = np.abs(max_data - min_data)
            ylim = [min_data - range * 0.1, max_data + range * 0.1]

        elif axes.get_yscale() in ['log', 'abslog']:
            abs_y = np.abs(y)
            abs_y = abs_y[np.nonzero(abs_y)]  # exclude 0's
            abs_y = abs_y[np.isfinite(abs_y)]  # exclude nan, inf

            if abs_y.size == 0:  # if no data, assign
                ylim = [10**-7, 10**-1]
            else:
                min_data = np.ceil(np.log10(np.min(abs_y)))
                max_data = np.floor(np.log10(np.max(abs_y)))

                ylim = [10**(min_data - 1), 10**(max_data + 1)]

        elif axes.get_yscale() == 'bisymlog':
            min_sign = np.sign(min_data)
            max_sign = np.sign(max_data)

            if min_sign > 0:
                min_data = np.ceil(np.log10(np.abs(min_data)))
            elif min_data == 0 or max_data == 0:
                pass
            else:
                min_data = np.floor(np.log10(np.abs(min_data)))

            if max_sign > 0:
                max_data = np.floor(np.log10(np.abs(max_data)))
            elif min_data == 0 or max_data == 0:
                pass
            else:
                max_data = np.ceil(np.log10(np.abs(max_data)))

            # TODO: ylim could be incorrect for neg/neg, checked for pos/pos, pos/neg
            ylim = [
                min_sign * 10**(min_data - min_sign),
                max_sign * 10**(max_data + max_sign)
            ]

        if ylim != axes.get_ylim():  # if ylim changes, update
            axes.set_ylim(ylim)

    def update_xylim(self, axes, xlim=[], ylim=[], force_redraw=True):
        data = self._get_data(axes)

        # on creation, there is no data, don't update
        for axis in ['x', 'y']:
            data_vec = data[axis]
            if np.count_nonzero(np.isfinite(data_vec)) < 2:
                return  # check size of vector, return if < 2

        for (axis, lim) in zip(['x', 'y'], [xlim, ylim]):
            # Set Limits
            if len(lim) == 0:
                eval('self.set_' + axis + 'lim(axes, data["' + axis + '"])')
            else:
                eval('axes.set_' + axis + 'lim(lim)')

            # If bisymlog, also update scaling, C
            if eval('axes.get_' + axis + 'scale()') == 'bisymlog':
                self._set_scale(axis, 'bisymlog', axes)
            ''' # TODO: Do this some day, probably need to create 
                        annotation during canvas creation
            # Move exponent 
            exp_loc = {'x': (.89, .01), 'y': (.01, .96)}
            eval(f'axes.get_{axis}axis().get_offset_text().set_visible(False)')
            ax_max = eval(f'max(axes.get_{axis}ticks())')
            oom = np.floor(np.log10(ax_max)).astype(int)
            axes.annotate(fr'$\times10^{oom}$', xy=exp_loc[axis], 
                          xycoords='axes fraction')
            '''

        if force_redraw:
            self._draw_event()  # force a draw

    def _get_data(self, axes):  # NOT Generic
        # get experimental data for axes
        data = {'x': [], 'y': []}
        if 'exp_data' in axes.item:
            data_plot = axes.item['exp_data'].get_offsets().T
            if np.shape(data_plot)[1] > 1:
                data['x'] = data_plot[0, :]
                data['y'] = data_plot[1, :]

            # append sim_x if it exists
            if 'sim_data' in axes.item and hasattr(axes.item['sim_data'],
                                                   'raw_data'):
                if axes.item['sim_data'].raw_data.size > 0:
                    data['x'] = np.append(data['x'],
                                          axes.item['sim_data'].raw_data[:, 0])

        elif 'weight_unc_fcn' in axes.item:
            data['x'] = axes.item['weight_unc_fcn'].get_xdata()
            data['y'] = axes.item['weight_unc_fcn'].get_ydata()

        elif any(key in axes.item
                 for key in ['density', 'qq_data', 'sim_data']):
            name = np.intersect1d(['density', 'qq_data'],
                                  list(axes.item.keys()))[0]
            for n, coord in enumerate(['x', 'y']):
                xyrange = np.array([])
                for item in axes.item[name]:
                    if name == 'qq_data':
                        coordData = item.get_offsets()
                        if coordData.size == 0:
                            continue
                        else:
                            coordData = coordData[:, n]
                    elif name == 'density':
                        coordData = eval('item.get_' + coord + 'data()')

                    coordData = np.array(coordData)[np.isfinite(coordData)]
                    if coordData.size == 0:
                        continue

                    xyrange = np.append(
                        xyrange,
                        [coordData.min(), coordData.max()])

                xyrange = np.reshape(xyrange, (-1, 2))
                data[coord] = [np.min(xyrange[:, 0]), np.max(xyrange[:, 1])]

        return data

    def _set_scale(self, coord, type, event, update_xylim=False):
        # find correct axes
        axes = self._find_calling_axes(event)
        # for axes in self.ax:
        # if axes == event or (hasattr(event, 'inaxes') and event.inaxes == axes):
        # break

        # Set scale menu boolean
        if coord == 'x':
            shared_axes = axes.get_shared_x_axes().get_siblings(axes)
        else:
            shared_axes = axes.get_shared_y_axes().get_siblings(axes)

        for shared in shared_axes:
            shared.scale[coord] = dict.fromkeys(shared.scale[coord],
                                                False)  # sets all types: False
            shared.scale[coord][type] = True  # set selected type: True

        # Apply selected scale
        if type == 'linear':
            str = 'axes.set_{:s}scale("{:s}")'.format(coord, 'linear')
        elif type == 'log':
            str = 'axes.set_{0:s}scale("{1:s}", nonpos{0:s}="mask")'.format(
                coord, 'log')
        elif type == 'abslog':
            str = 'axes.set_{:s}scale("{:s}")'.format(coord, 'abslog')
        elif type == 'bisymlog':
            # default string to evaluate
            str = 'axes.set_{0:s}scale("{1:s}")'.format(coord, 'bisymlog')

            data = self._get_data(axes)[coord]
            if len(data) != 0:
                finite_data = np.array(data)[np.isfinite(
                    data)]  # ignore nan and inf
                C = self.bisymlog.set_C_heuristically(finite_data)

                str = 'axes.set_{0:s}scale("{1:s}", C={2:e})'.format(
                    coord, 'bisymlog', C)

        eval(str)
        if type == 'linear' and coord == 'x':
            formatter = MathTextSciSIFormatter(useOffset=False,
                                               useMathText=True)
            axes.xaxis.set_major_formatter(formatter)

        elif type == 'linear' and coord == 'y':
            formatter = mpl.ticker.ScalarFormatter(useOffset=False,
                                                   useMathText=True)
            formatter.set_powerlimits([-3, 4])
            axes.yaxis.set_major_formatter(formatter)

        if update_xylim:
            self.update_xylim(axes)

    def _animate_items(self, bool=True):
        for axis in self.ax:
            if axis.animateAxisLabels:
                axis.xaxis.set_animated(bool)
                axis.yaxis.set_animated(bool)

            if axis.get_legend() is not None:
                axis.get_legend().set_animated(bool)

            for item in axis.item.values():
                if isinstance(item, list):
                    for subItem in item:
                        if isinstance(subItem, dict):
                            subItem['line'].set_animated(bool)
                        else:
                            subItem.set_animated(bool)
                else:
                    item.set_animated(bool)

    def _draw_items_artist(self):
        self.canvas.restore_region(self.background_data)
        for axis in self.ax:
            if axis.animateAxisLabels:
                axis.draw_artist(axis.xaxis)
                axis.draw_artist(axis.yaxis)
            for item in axis.item.values():
                if isinstance(item, list):
                    for subItem in item:
                        if isinstance(subItem, dict):
                            axis.draw_artist(subItem['line'])
                        else:
                            axis.draw_artist(subItem)
                else:
                    axis.draw_artist(item)

            if axis.get_legend() is not None:
                axis.draw_artist(axis.get_legend())

        self.canvas.update()

    def set_background(self):
        self.canvas.mpl_disconnect(self._draw_event_signal)
        self.canvas.draw(
        )  # for when shock changes. Without signal disconnect, infinite loop
        self._draw_event_signal = self.canvas.mpl_connect(
            'draw_event', self._draw_event)
        self.background_data = self.canvas.copy_from_bbox(self.fig.bbox)

    def _draw_event(
        self,
        event=None
    ):  # After redraw (new/resizing window), obtain new background
        self._animate_items(True)
        self.set_background()
        self._draw_items_artist()
        #self.canvas.draw_idle()

    def clear_plot(self, ignore=[], draw=True):
        for axis in self.ax:
            if axis.get_legend() is not None:
                axis.get_legend().remove()

            for item in axis.item.values():
                if hasattr(item, 'set_offsets'):  # clears all data points
                    if 'scatter' not in ignore:
                        item.set_offsets(([np.nan, np.nan]))
                elif hasattr(item, 'set_xdata') and hasattr(item, 'set_ydata'):
                    if 'line' not in ignore:
                        item.set_xdata([np.nan, np.nan])  # clears all lines
                        item.set_ydata([np.nan, np.nan])
                elif hasattr(item, 'set_text'):  # clears all text boxes
                    if 'text' not in ignore:
                        item.set_text('')
        if draw:
            self._draw_event()

    def click(self, event):
        if event.button == 3:  # if right click
            if not self.toolbar.mode:
                self._popup_menu(event)
            # if self.toolbar._active is 'ZOOM':  # if zoom is on, turn off
            # self.toolbar.press_zoom(event)  # cancels current zooom
            # self.toolbar.zoom()             # turns zoom off
            elif event.dblclick:  # if double right click, go to default view
                self.toolbar.home()

    def key_press(self, event):
        if event.key == 'escape':
            if self.toolbar.mode == 'zoom rect':  # if zoom is on, turn off
                self.toolbar.zoom()  # turns zoom off
            elif self.toolbar.mode == 'pan/zoom':
                self.toolbar.pan()
        # elif event.key == 'shift':
        elif event.key == 'x':  # Does nothing, would like to make sticky constraint zoom/pan
            self.x_zoom_constraint = not self.x_zoom_constraint
        elif event.key == 'y':  # Does nothing, would like to make sticky constraint zoom/pan
            self.y_zoom_constraint = not self.y_zoom_constraint
        elif event.key in ['s', 'l', 'L', 'k']:
            pass
        else:
            key_press_handler(event, self.canvas, self.toolbar)

    # def key_release(self, event):
    # print(event.key, 'released')

    def NavigationToolbar(self, *args, **kwargs):
        ## Add toolbar ##
        self.toolbar = CustomNavigationToolbar(self.canvas,
                                               self.widget,
                                               coordinates=True)
        self.mpl_layout.addWidget(self.toolbar)

    def _popup_menu(self, event):
        axes = self._find_calling_axes(event)  # find axes calling right click
        if axes is None: return

        pos = self.parent.mapFromGlobal(QtGui.QCursor().pos())

        popup_menu = QMenu(self.parent)
        xScaleMenu = popup_menu.addMenu('x-scale')
        yScaleMenu = popup_menu.addMenu('y-scale')

        for coord in ['x', 'y']:
            menu = eval(coord + 'ScaleMenu')
            for type in axes.scale[coord].keys():
                action = QAction(type, menu, checkable=True)
                if axes.scale[coord][type]:  # if it's checked
                    action.setEnabled(False)
                else:
                    action.setEnabled(True)
                menu.addAction(action)
                action.setChecked(axes.scale[coord][type])
                fcn = lambda event, coord=coord, type=type: self._set_scale(
                    coord, type, axes, True)
                action.triggered.connect(fcn)

        # Create menu for AutoScale options X Y All
        popup_menu.addSeparator()
        autoscale_options = ['AutoScale X', 'AutoScale Y', 'AutoScale All']
        for n, text in enumerate(autoscale_options):
            action = QAction(text, menu, checkable=True)
            if n < len(self.autoScale):
                action.setChecked(self.autoScale[n])
            else:
                action.setChecked(all(self.autoScale))
            popup_menu.addAction(action)
            action.toggled.connect(
                lambda event, n=n: self._setAutoScale(n, event, axes))

        popup_menu.exec_(self.parent.mapToGlobal(pos))

    def _setAutoScale(self, choice, event, axes):
        if choice == len(self.autoScale):
            for n in range(len(self.autoScale)):
                self.autoScale[n] = event
        else:
            self.autoScale[choice] = event

        if event:  # if something toggled true, update limits
            self.update_xylim(axes)