def bsearch(self, pname: str, clevel: float, acc: int, direct: int = 1, idx=None) -> float: """Binary search for confidence limit Args ---- pname : str parameter name clevel: float value of log of objective function at confidence limit acc: int accuracy in terms of the number of significant figures to consider Keywords -------- direct : int, optional direction to search (0=downwards, 1=upwards), by default 1 idx: optional for indexed parameters, the value of the index to get the confidence limits for Returns ------- float value of parameter bound """ from plepy.helper import sigfig, sflag # manually change parameter of interest if idx is None: self.plist[pname].fix() x_out = float(self.pbounds[pname][direct]) x_in = float(self.popt[pname]) else: self.plist[pname][idx].fix() x_out = float(self.pbounds[pname][idx][direct]) x_in = float(self.popt[pname][idx]) # Initialize values based on direction x_mid = x_out # for upper CI search if direct: x_high = x_out x_low = x_in plc = 'upper' puc = 'Upper' no_lim = float(x_out) # for lower CI search else: x_high = x_in x_low = x_out plc = 'lower' puc = 'Lower' no_lim = float(x_out) # Print search info print(' ' * 80) print('Parameter: {:s}'.format(pname)) if idx is not None: print('Index: {:s}'.format(str(idx))) print('Bound: {:s}'.format(puc)) print(' ' * 80) # check convergence criteria ctol = sigfig(x_high, acc) - sigfig(x_low, acc) # Find outermost feasible value # evaluate at outer bound r_mid = self.m_eval(pname, x_mid, idx=idx) fcheck = sflag(r_mid) self.m.solutions.load_from(r_mid) err = np.log(penv.value(self.m.obj)) # If solution is feasible and the error is less than the value # at the confidence limit, there is no CI in that direction. # Set to bound. if fcheck == 0 and err < clevel: pCI = no_lim print('No %s CI! Setting to %s bound.' % (plc, plc)) else: fiter = 0 # If solution is infeasible, find a new value for x_out # that is feasible and above the confidence threshold. while (fcheck == 1 or err < clevel) and ctol > 0.0: print('f_iter: %i, x_high: %f, x_low: %f' % (fiter, x_high, x_low)) # check convergence criteria ctol = sigfig(x_high, acc) - sigfig(x_low, acc) # evaluate at midpoint x_mid = 0.5 * (x_high + x_low) r_mid = self.m_eval(pname, x_mid, idx) fcheck = sflag(r_mid) # if infeasible, continue search inward from current # midpoint if fcheck == 1: x_out = float(x_mid) self.m.solutions.load_from(r_mid) err = np.log(penv.value(self.m.obj)) # if feasbile, but not over CL threshold, continue # search outward from current midpoint if fcheck == 0 and err < clevel: x_in = float(x_mid) if direct: x_high = x_out x_low = x_in else: x_high = x_in x_low = x_out fiter += 1 # if convergence reached, there is no upper CI if ctol == 0.0: pCI = no_lim print('No %s CI! Setting to %s bound.' % (plc, plc)) # otherwise, find the upper CI between outermost feasible # pt and optimal solution using binary search else: x_out = float(x_mid) if direct: x_high = x_out x_low = x_in else: x_high = x_in x_low = x_out biter = 0 # repeat until convergence criteria is met # (i.e. x_high = x_low) while ctol > 0.0: print('b_iter: %i, x_high: %f, x_low: %f' % (biter, x_high, x_low)) # check convergence criteria ctol = sigfig(x_high, acc) - sigfig(x_low, acc) # evaluate at midpoint x_mid = 0.5 * (x_high + x_low) r_mid = self.m_eval(pname, x_mid, idx=idx) fcheck = sflag(r_mid) self.m.solutions.load_from(r_mid) err = np.log(penv.value(self.m.obj)) print(self.popt[pname]) biter += 1 # if midpoint infeasible, continue search inward if fcheck == 1: x_out = float(x_mid) # if midpoint over CL, continue search inward elif err > clevel: x_out = float(x_mid) # if midpoint under CL, continue search outward else: x_in = float(x_mid) if direct: x_high = x_out x_low = x_in else: x_high = x_in x_low = x_out pCI = sigfig(x_mid, acc) print('%s CI of %f found!' % (puc, pCI)) # reset parameter self.setval(pname, self.popt[pname]) if idx is None: self.plist[pname].free() else: self.plist[pname][idx].free() print(self.popt[pname]) return pCI
def inner_loop(xopt, xb, direct=1, idx=None) -> dict: from plepy.helper import sflag pdict = {} if direct: print('Going up...') x0 = np.linspace(xopt, xb, n + 2, endpoint=True) else: print('Going down...') x0 = np.linspace(xb, xopt, n + 2, endpoint=True) print('x0:', x0) # evaluate objective at each discretization point for w, x in enumerate(x0): xdict = {} if w == 0: for p in self.pnames: self.setval(p, self.popt[p]) else: for p in self.pnames: prevx = pdict[x0[w - 1]][p] self.setval(p, prevx) try: rx = self.m_eval(pname, x, idx=idx, reset=False) xdict['flag'] = sflag(rx) self.m.solutions.load_from(rx) xdict['obj'] = np.log(penv.value(self.m.obj)) # store values of other parameters at each point for p in self.pnames: xdict[p] = self.getval(p) except ValueError: xdict = copy.deepcopy(pdict[x0[w - 1]]) pdict[x] = xdict if direct: x_out = x0[1:] x_in = x0[:-1] else: x_out = x0[:-1] x_in = x0[1:] # calculate magnitude of step sizes dx = x_out - x_in y0 = np.array([pdict[x]['obj'] for x in x0]) print('y0:', y0) if direct: y_out = y0[1:] y_in = y0[:-1] else: y_out = y0[:-1] y_in = y0[1:] # calculate magnitude of objective value changes between # each step dy = np.abs(y_out - y_in) # pull indices where objective value change is greater than # threshold value (dtol) and step size is greater than # minimum ierr = [(i > dtol and j > min_step) for i, j in zip(dy, dx)] print('ierr:', ierr) itr = 0 # For intervals of large change (above dtol), calculate # values at midpoint. Repeat until no large changes or # minimum step size is reached. while len(ierr) != 0: print('iter: %i' % (itr)) x_oerr = np.array([j for i, j in zip(ierr, x_out) if i]) x_ierr = np.array([j for i, j in zip(ierr, x_in) if i]) x_mid = 0.5 * (x_oerr + x_ierr) for w, x in enumerate(x_mid): xdict = {} for p in self.pnames: prevx = pdict[x_ierr[w]][p] self.setval(p, prevx) try: rx = self.m_eval(pname, x, idx=idx, reset=False) xdict['flag'] = sflag(rx) self.m.solutions.load_from(rx) xdict['obj'] = np.log(penv.value(self.m.obj)) # store values of other parameters at each pt for p in self.pnames: xdict[p] = self.getval(p) except ValueError: xdict = copy.deepcopy(pdict[x_ierr[w]]) pdict[x] = xdict # get parameter values needed to calculate change in # error over intervals that have not converged x0 = np.array(sorted(set([*x_oerr, *x_mid, *x_ierr]))) print('x0:', x0) x_out = x0[1:] x_in = x0[:-1] # calculate magnitude of step sizes dx = x_out - x_in y0 = np.array([pdict[x]['obj'] for x in x0]) print('y0:', y0) y_out = y0[1:] y_in = y0[:-1] # calculate magnitude of objective value change between # each step dy = np.abs(y_out - y_in) # pull indices where objective value change is greater # than threshold value (dtol) and step size is greater # than minimum ierr = [(i > dtol and j > min_step) for i, j in zip(dy, dx)] print('ierr:', ierr) itr += 1 return pdict