def _piecewise_symbolic_integral(cache, integrand, x, y=None): """ Computes the symbolic integral of 'x' of a piecewise polynomial 'integrand'. The result might be a sympy expression or a numerical value. Parameters ---------- integrand : list A list of (lower bound, upper bound, polynomial) x : object A string/sympy expression representing the integration variable """ cache_hit = [0, 0] if (cache is not None) else None res = 0 for l, u, p in integrand: symx = symvar(x) symy = symvar(y) if y else symvar("aux_y") syml = Poly(to_sympy(l), symy, domain="QQ") symu = Poly(to_sympy(u), symy, domain="QQ") if type(p) != Poly: symp = Poly(to_sympy(p), symx, domain="QQ") else: symp = Poly(p.as_expr(), symx, symy, domain="QQ") #print("integrating", symp.as_expr(), f"in d{symx} with bounds", [syml.as_expr(), symu.as_expr()]) if cache is not None: # for cache = True """ hierarchical cache, where we cache: - the anti-derivatives for integrands, retrieved by: (None, None, integrand key) - the partial integration term, retrieved by: (lower bound key, None, integrand key) (None, upper bound key, integrand key) - the whole integration, retrieved by: (lower bound key, upper bound key, integrand key) """ # cache keys for bounds k_lower = MP2WMI.sympy_to_tuple(syml) k_upper = MP2WMI.sympy_to_tuple(symu) k_poly = MP2WMI.sympy_to_tuple( symp) # cache key for integrand polynomial k_full = (k_lower, k_upper, k_poly) #print("========= KEYS =========") #print("lower:", syml.as_expr(), "-->", k_lower) #print("upper:", symu.as_expr(), "-->", k_upper) #print("poly:", symp.as_expr(), "-->", k_poly) #print("========================") if k_full in cache: # retrieve the whole integration cache_hit[True] += 1 symintegral = MP2WMI.tuple_to_sympy( cache[k_full], symx, symy) symintegral = symintegral.subs(symintegral.gens[0], symy) else: # retrieve partial integration terms terms = [None, None] k_part_l = (k_lower, k_poly) k_part_u = (k_upper, k_poly) if k_part_l in cache: partial_l = MP2WMI.tuple_to_sympy( cache[k_part_l], symx, symy) terms[0] = partial_l.subs(partial_l.gens[0], symy) if k_part_u in cache: partial_u = MP2WMI.tuple_to_sympy( cache[k_part_u], symx, symy) terms[1] = partial_u.subs(partial_u.gens[0], symy) if None not in terms: cache_hit[True] += 1 else: # retrieve anti-derivative k_anti = (k_poly, ) if k_anti in cache: cache_hit[True] += 1 antidrv = MP2WMI.tuple_to_sympy( cache[k_anti], symx, symy) else: cache_hit[False] += 1 antidrv = symp.integrate(symx) cache[k_anti] = MP2WMI.sympy_to_tuple(antidrv) # cache partial integration terms if terms[0] is None: terms[0] = Poly(antidrv.as_expr(), symx, domain=f'QQ[{symy}]').eval( {symx: syml.as_expr()}) terms[0] = Poly(terms[0].as_expr(), symx, symy, domain="QQ") cache[k_part_l] = MP2WMI.sympy_to_tuple(terms[0]) if terms[1] is None: terms[1] = Poly(antidrv.as_expr(), symx, domain=f'QQ[{symy}]').eval( {symx: symu.as_expr()}) terms[1] = Poly(terms[1].as_expr(), symx, symy, domain="QQ") cache[k_part_u] = MP2WMI.sympy_to_tuple(terms[1]) #print("subs: (", terms[1].as_expr(), ") - (", terms[0].as_expr(), ")") symintegral = terms[1] - terms[0] if not isinstance(symintegral, Poly): symintegral = Poly(symintegral, symx, symy, domain='QQ') cache[k_full] = MP2WMI.sympy_to_tuple(symintegral) else: # for cache = False antidrv = symp.integrate(symx) lower = Poly(antidrv.as_expr(), symx, domain=f'QQ[{symy}]').eval({symx: syml.as_expr()}) lower = Poly(lower.as_expr(), symx, symy, domain="QQ") upper = Poly(antidrv.as_expr(), symx, domain=f'QQ[{symy}]').eval({symx: symu.as_expr()}) upper = Poly(upper.as_expr(), symx, symy, domain="QQ") symintegral = upper - lower res += symintegral #print("integral:", symintegral.as_expr()) #print() #print("RESULT:", res) #print("**************************************************") return res, cache_hit
def piecewise_symbolic_integral(self, integrand, x, y=None): """ Computes the symbolic integral of 'x' of a piecewise polynomial 'integrand'. The result might be a sympy expression or a numerical value. Parameters ---------- integrand : list A list of (lower bound, upper bound, polynomial) x : object A string/sympy expression representing the integration variable """ res = 0 #logger.debug(f"\t\t\t\tpiecewise_symbolic_integral") #logger.debug(f"\t\t\t\tlen(integrand): {len(integrand)} --- y: {y}") for l, u, p in integrand: symx = symvar(x) symy = symvar(y) if y else symvar("aux_y") syml = Poly(to_sympy(l), symy, domain="QQ") symu = Poly(to_sympy(u), symy, domain="QQ") #logger.debug(f"\t\t\t\t\tl: {l} --- u: {u} --- p: {p}") if type(p) != Poly: symp = Poly(to_sympy(p), symx, domain="QQ") else: symp = Poly(p.as_expr(), symx, domain=f"QQ[{symy}]") if y else p if self.cache is not None: # for cache = True """ hierarchical cache, where we cache: - the anti-derivatives for integrands, retrieved by the same integrand key - the partial integration term, retrieved by the same (integrand key, lower / upper bound key) pair - the whole integration, retrieved by the same (integrand key, lower bound key, upper bound key) pair """ bds_ks = [MPWMI.cache_key(syml)[0], MPWMI.cache_key(symu)[0]] # cache keys for bounds bds = [syml.as_expr(), symu.as_expr()] p_ks = MPWMI.cache_key(symp) # cache key for integrand polynomial trm_ks = [(bds_ks[0], p_ks[0]), (bds_ks[1], p_ks[0])] if (bds_ks[0], bds_ks[1], p_ks[0]) in self.cache: # retrieve the whole integration self.cache_hit[True] += 1 symintegral = self.cache[(bds_ks[0], bds_ks[1], p_ks[0])] symintegral = symintegral.subs(symintegral.gens[0], symy) else: terms = [] for tk in trm_ks: # retrieve partial integration terms if tk in self.cache: trm = self.cache[tk] trm = trm.subs(trm.gens[0], symy) terms.append(trm) else: terms.append(None) if None not in terms: self.cache_hit[True] += 1 else: if p_ks[0] in self.cache: # retrieve anti-derivative self.cache_hit[True] += 1 antidrv = self.cache[p_ks[0]] antidrv_expr = antidrv.as_expr().subs(antidrv.gens[0], symx) antidrv = Poly(antidrv_expr, symx, domain=f"QQ[{symy}]") if y \ else Poly(antidrv_expr, symx, domain="QQ") else: self.cache_hit[False] += 1 antidrv = symp.integrate(symx) for k in p_ks: # cache anti-derivative self.cache[k] = antidrv for i in range(len(terms)): if terms[i] is not None: continue terms[i] = antidrv.eval({symx: bds[i]}) terms[i] = Poly(terms[i].as_expr(), symy, domain="QQ") for k in p_ks: # cache partial integration terms self.cache[(bds_ks[i], k)] = terms[i] symintegral = terms[1] - terms[0] for k in p_ks: # cache the whole integration self.cache[(bds_ks[0], bds_ks[1], k)] = symintegral else: # for cache = False antidrv = symp.integrate(symx) symintegral = antidrv.eval({symx: symu.as_expr()}) - \ antidrv.eval({symx: syml.as_expr()}) res += symintegral #logger.debug(f"\t\t\t\t\tsymintegral: {symintegral}") return res