def M_formula(power, tau = True): r""" Generate the formula for the conditional moments with second-order corrections based on the relation with the ordinary Bell polynomials .. math:: M_n(x^{\prime},\tau) \sim (n!)\tau D_n(x^{\prime}) + \frac{(n!)\tau^2}{2} \sum_{m=1}^{n-1} D_m(x^{\prime}) D_{n-m}(x^{\prime}) Parameters ---------- power: int Desired order of the formula. Returns ------- term: sympy.symbols Expression up to given ``power``. """ init_sym = symbols('D:'+str(int(power+1)))[1:] sym = () for i in range(1,power + 1): sym += (factorial(i)*init_sym[i-1],) if tau == True: t = symbols('tau') term = t*bell(power, 1, sym) + t**2*bell(power, 2, sym) else: term = bell(power, 1, sym) + bell(power, 2, sym) return term
def test_bell(): assert [bell(n) for n in range(8)] == [1, 1, 2, 5, 15, 52, 203, 877] assert bell(0, x) == 1 assert bell(1, x) == x assert bell(2, x) == x**2 + x assert bell(5, x) == x**5 + 10 * x**4 + 25 * x**3 + 15 * x**2 + x X = symbols('x:6') # X = (x0, x1, .. x5) # at the same time: X[1] = x1, X[2] = x2 for standard readablity. # but we must supply zero-based indexed object X[1:] = (x1, .. x5) assert bell(6, 2, X[1:]) == 6 * X[5] * X[1] + 15 * X[4] * X[2] + 10 * X[3]**2 assert bell( 6, 3, X[1:]) == 15 * X[4] * X[1]**2 + 60 * X[3] * X[2] * X[1] + 15 * X[2]**3 X = (1, 10, 100, 1000, 10000) assert bell(6, 2, X) == (6 + 15 + 10) * 10000 X = (1, 2, 3, 3, 5) assert bell(6, 2, X) == 6 * 5 + 15 * 3 * 2 + 10 * 3**2 X = (1, 2, 3, 5) assert bell(6, 3, X) == 15 * 5 + 60 * 3 * 2 + 15 * 2**3
def test_bell(): assert [bell(n) for n in range(8)] == [1, 1, 2, 5, 15, 52, 203, 877] assert bell(0, x) == 1 assert bell(1, x) == x assert bell(2, x) == x**2 + x assert bell(5, x) == x**5 + 10*x**4 + 25*x**3 + 15*x**2 + x X = symbols('x:6') # X = (x0, x1, .. x5) # at the same time: X[1] = x1, X[2] = x2 for standard readablity. # but we must supply zero-based indexed object X[1:] = (x1, .. x5) assert bell(6, 2, X[1:]) == 6*X[5]*X[1] + 15*X[4]*X[2] + 10*X[3]**2 assert bell( 6, 3, X[1:]) == 15*X[4]*X[1]**2 + 60*X[3]*X[2]*X[1] + 15*X[2]**3 X = (1, 10, 100, 1000, 10000) assert bell(6, 2, X) == (6 + 15 + 10)*10000 X = (1, 2, 3, 3, 5) assert bell(6, 2, X) == 6*5 + 15*3*2 + 10*3**2 X = (1, 2, 3, 5) assert bell(6, 3, X) == 15*5 + 60*3*2 + 15*2**3
def test_bell(): assert [bell(n) for n in range(8)] == [1, 1, 2, 5, 15, 52, 203, 877] assert bell(0, x) == 1 assert bell(1, x) == x assert bell(2, x) == x**2 + x assert bell(5, x) == x**5 + 10 * x**4 + 25 * x**3 + 15 * x**2 + x
def test_bell(): assert [bell(n) for n in range(8)] == [1, 1, 2, 5, 15, 52, 203, 877] assert bell(0, x) == 1 assert bell(1, x) == x assert bell(2, x) == x**2 + x assert bell(5, x) == x**5 + 10*x**4 + 25*x**3 + 15*x**2 + x
def F_formula(power): r""" Generate the formula for the conditional moments with second-order corrections based on the relation with the ordinary Bell polynomials .. math:: D_n(x) &= \frac{1}{\tau (n!)} \bigg[ \hat{B}_{n,1} \left(M_1(x,\tau),M_2(x,\tau),\ldots,M_{n}(x,\tau)\right) \\ &\qquad \left.-\frac{\tau}{2} \hat{B}_{n,2} \left(M_1(x,\tau),M_2(x,\tau),\ldots,M_{n-1}(x,\tau)\right)\right]. Parameters ---------- power: int Desired order of the formula. Returns ------- term: sympy.symbols Expression up to given ``power``. """ init_sym = symbols('D:'+str(int(power+1)))[1:] sym = () for i in range(1,power + 1): sym += (factorial(i)*init_sym[i-1],) term = (symbols('M'+str(int(power))) - bell(power, 2, sym))/factorial(power) return term
def get_n_der_vecs(dk_f, gx, N): """ This function applies Faa di Bruno's formula to compute the derivatives of order from 1 to N given the calling elementary operator has dk_f as its kth order derivative function. INPUTS ======= dk_f(val, k): A lambda function of the kth order derivative of f at the point val gx: Potentially an AutoDiff object N: highest derivative order of gx RETURNS ======= a list of high-order derivatives up until gx.N """ # Create symbols and symbol-value mapping for eval() in the loop dxs = symbols('q:%d' % N) dx_mapping = {str(dxs[i]): gx.der[i] for i in range(N)} # Use Faa di Bruno's formula der_new = [] for n in range(1, N + 1): nth_der = 0 for k in range(1, n + 1): # The first n-k+1 derivatives t = n - k + 1 vars = dxs[:t] # bell polynomial as python function str bell_nk_str = str(bell(n, k, vars)) # evaluate the bell polynomial using the symbol-value mapping val_bell_nk = eval(bell_nk_str, dx_mapping) nth_der += dk_f(gx.val, k) * val_bell_nk der_new.append(nth_der) return der_new
def bellyn(self, nmax): Lbell = [[0] * nmax for i in range(nmax)] Lbell[0][0] = 0 for n in range(1, nmax): for k in range(1, n + 1): Lbell[n][k] = bell(n, k, self.kminus) return Lbell
def kappac(self, n): fn = self.fn r = self.r z = self.z y = self.y W = self.W Q = self.Q S = self.S NBc = self.NBc NBbc = self.NBbc j = self.j L1 = [fn[0] * bell(n, 1, (r))] for k in range(2, n + 1): L1.extend([fn[k - 1] * bell(n, k, r)]) L2 = Sum(Indexed('y', j), (j, 0, n - 1)) L22 = lambdify(y, L2) L3 = L22(L1) L4 = L3.subs({W(z): Q(z) * S(z) - NBc * NBbc}) L5 = L4.subs({S(z): NBc + NBbc, Q(z): z * z - NBc * NBbc}) return L5
def _derivative_transformation_matrix(deriv_func_list: list, point: float, order: int): r"""Compute transformation from one variable to another. Suppose there is a function :math:`f(x)` and a :math:`N`-th differentiable transformation :math:`g(x) = r` from variable :math:`x` to variable :math:`r`. This function returns a matrix that converts the derivatives :math:`[\frac{df}{dx}, \cdots, \frac{d^N f}{dx^N}]^T` by matrix multiplication to the derivatives of the new transformation :math:`[\frac{df}{dr}, \cdots, \frac{d^Nf}{dr^N}]^T`. It is a matrix with (i,j)-th entries: .. math:: m_{i, j} = \begin{cases} B_{i,j}\bigg(\frac{dg}{dx}, \cdots, \frac{d^{k-j+1} g}{dx^{k-j+1}} \bigg) & i \leq j \\ 0 & \text{else} \end{cases} Parameters ---------- deriv_func_list : list[K] List of size :math:`K` callable functions representing the derivatives of :math:`g(x)`, i.e. :math:`[\frac{dg}{dx}, \cdots, \frac{d^K g}{dx^K}]`. point : float The point in :math:`r`, in which the derivatives are being calculated at. order : int The number of derivatives from 1 to `order` that are going to be transformed. Returns ------- ndarray((order, order)) Returns the matrix that converts derivatives of one domain to another. """ numb_derivs = len(deriv_func_list) if order > numb_derivs: raise ValueError("TODO") # Calculate derivatives of transformation evaluated at the point derivs_at_pt = np.array([dev(point) for dev in deriv_func_list], dtype=np.float64) deriv_transf = np.zeros((order, order)) for i in range(0, order): for j in range(0, i + 1): deriv_transf[i, j] = float(bell(i + 1, j + 1, derivs_at_pt)) return deriv_transf
def chain_rule(ad, new_val, der, der2, higher_der=None): """ Applies chain rule to returns a new AD object with correct value and derivatives. Parameters: ad (AD): An AD object new_val (float): Value of the new AD object der (float): Derivative of the outer function in chain rule Returns: new_ad (AD): a new AD object with correct value and derivatives Example: >>> import AD as AD >>> x = AD.AD(2,order=5) >>> newad = 5*x**3+2 >>> higherde = np.array([60,60,30,0,0]) >>> chain_rule(x, 42, 60, 60, higher_der=higherde) AD(value: [42], derivatives: [60.]) """ new_der = der * ad.der new_der2 = der * ad.der2 + der2 * np.matmul( np.array([ad.der]).T, np.array([ad.der])) if ad.higher is None: new_ad = AD.AD(new_val, tag=ad.tag, der=new_der, der2=new_der2) else: new_higher_der = np.array([0.0] * len(ad.higher)) new_higher_der[0] = new_der new_higher_der[1] = new_der2 for i in range(2, len(ad.higher)): n = i + 1 sum = 0 for k in range(1, n + 1): sum += higher_der[k - 1] * sp.bell(n, k, ad.higher[0:n - k + 1]) new_higher_der[i] = sum new_ad = AD.AD(new_val, tag=ad.tag, der=new_der, der2=new_der2, order=len(ad.higher)) new_ad.higher = new_higher_der return new_ad
def grand_potential_derivative(self, n_elec, order=1): r""" Evaluate the :math:`n^{\text{th}}`-order derivative of grand potential at the given n_elec. This returns the :math:`n^{\text{th}}`-order derivative of grand potential model w.r.t. to the chemical potential, at fixed external potential, evaluated for the specified number of electrons :math:`N_{\text{elec}}`. That is, .. math:: \left. \left(\frac{\partial^n \Omega}{\partial \mu^n} \right)_{v(\mathbf{r})} \right|_{N = N_{\text{elec}}} = \left. \left(\frac{\partial^{n-1}}{\partial \mu^{n-1}} \frac{\partial \Omega}{\partial \mu} \right)_{v(\mathbf{r})} \right|_{N = N_{\text{elec}}} = - \left. \left(\frac{\partial^{n-1} N}{\partial \mu^{n-1}} \right)_{v(\mathbf{r})} \right|_{N = N_{\text{elec}}} \quad n = 1, 2, \dots These derivatives can be computed using the derivative of energy model w.r.t. number of electrons, at fixed external potential, evaluated at :math:`N_{\text{elec}}`. More specifically, .. math:: \left. \left(\frac{\partial \Omega}{\partial \mu} \right)_{v(\mathbf{r})} \right|_{N = N_{\text{elec}}} &= - N_{\text{elec}} \\ \left. \left(\frac{\partial^2 \Omega}{\partial \mu^2} \right)_{v(\mathbf{r})} \right|_{N = N_{\text{elec}}} &= -\frac{1}{\eta^{(1)}} where :math:`\eta^{(n)}` denotes the :math:`(n+1)^{\text{th}}`-order derivative of energy w.r.t. number of electrons evaluated at :math:`N_{\text{elec}}`, i.e. .. math:: \eta^{(n)} = \left. \left(\frac{\partial^{n+1} E}{\partial N^{n+1}} \right)_{v(\mathbf{r})} \right|_{N = N_{\text{elec}}} \qquad n = 1, 2, \dots To compute higher-order derivatives, Faa di Bruno formula which generalizes the chain rule to higher derivatives can be used. i.e. for :math:`n \geq 2`, .. math:: \left. \left(\frac{\partial^n \Omega}{\partial \mu^n} \right)_{v(\mathbf{r})} \right|_{N = N_{\text{elec}}} = \frac{-\displaystyle\sum_{k=1}^{n-2} \left.\left(\frac{\partial^k \Omega}{\partial \mu^k} \right)_{v(\mathbf{r})} \right|_{N = N_{\text{elec}}} \cdot B_{n-1,k} \left(\eta^{(1)}, \eta^{(2)}, \dots, \eta^{(n-k)} \right)} {B_{n-1,n-1} \left(\eta^{(1)}\right)} where :math:`B_{n-1,k} \left(x_1, x_2, \dots, x_{n-k}\right)` denotes the Bell polynomials. Parameters ---------- n_elec : float Number of electrons, :math:`N_{\text{elec}}`. order : int, default=1 The order of derivative denoted by :math:`n` in the formula. """ if n_elec is not None and n_elec < 0.0: raise ValueError( 'Number of electrons cannot be negativ! #elec={0}'.format( n_elec)) if not (isinstance(order, int) and order > 0): raise ValueError( 'Argument order should be an integer greater than or equal to 1.' ) if n_elec is None: deriv = None elif order == 1: # 1st order derivative is minus number of electrons deriv = -n_elec elif order == 2: # 2nd order derivative is inverse hardness hardness = self.energy_derivative(n_elec, order=2) if hardness is not None and hardness != 0.0: deriv = -1.0 / hardness else: deriv = None else: # higher-order derivatives are compute with Faa Di Bruno formula # list of hyper-hardneses (derivatives of energy w.r.t. N) e_deriv = [ self.energy_derivative(n_elec, i + 1) for i in xrange(1, order) ] g_deriv = [ self.grand_potential_derivative(n_elec, k + 1) for k in xrange(1, order - 1) ] if any([item is None for item in e_deriv]) or any( [item is None for item in g_deriv]): deriv = None else: deriv = 0 for k in xrange(1, order - 1): deriv -= g_deriv[k - 1] * sp.bell(order - 1, k, e_deriv[:order - k]) deriv /= sp.bell(order - 1, order - 1, [e_deriv[0]]) return deriv
def count(smilesdata, output='lib_data.txt', timeout=5, symmetry=True, sample_size=8000): ''' Count...? ''' x = [] compression = [] #counter = 0 line_count = 0 #sample_size=int(sample_size) for file_lines in open(smilesdata).readlines(): line_count += 1 if line_count <= sample_size: sample_idx = np.arange(line_count) else: sample_idx = np.random.choice(line_count, sample_size, replace=False) f = open(output, "w") f.write( '#SMILES heavy_atoms bond_number bell_number naive_count starsbars symmetry_count MOG_nodes \n' ) with open(smilesdata) as infile: for counter, line in enumerate(infile): if counter not in sample_idx: continue smiles = line.split()[1] #print(smiles) try: G = smiles2graph(smiles) except: continue mol = rdkit.Chem.MolFromSmiles(smiles) heavy_atoms = mol.GetNumHeavyAtoms() atoms = len(G) edges = G.number_of_edges() LG = chem_line_graph(G) bond_classes = equiv_classes(LG) atom_classes = equiv_classes(G, node_key='atom_type', edge_key='bond') bellnum = bell(atoms) - 1 naive_count = (2**edges) - 1 product = 1 for i in range(len(bond_classes)): product *= (len(bond_classes[i]) + 1) stars_bars = product - 1 symmetric_counts = (2**(len(bond_classes))) - 1 #print(G.number_of_edges(),len(atom_classes)) if (edges == 0) or (len(atom_classes) == 1): continue try: mog = MOG(smiles, symmetry, timeout) f.write( '{smiles} {h_atoms} {bond_number} {bell} {naive} {starsbars} {symmetric} {mog_nodes} \n' .format(smiles=smiles, h_atoms=heavy_atoms, bond_number=edges, bell=bellnum, naive=naive_count, starsbars=stars_bars, symmetric=symmetric_counts, mog_nodes=len(mog.graph))) except MOG.TimeoutError: f.write( '{smiles} {h_atoms} {bond_number} {bell} {naive} {starsbars} {symmetric} {mog_nodes} \n' .format(smiles=smiles, h_atoms=heavy_atoms, bond_number=edges, bell=bellnum, naive=naive_count, starsbars=stars_bars, symmetric=symmetric_counts, mog_nodes='NA')) continue f.close()
def _transform_ode_from_derivs(coeffs: Union[list, np.ndarray], deriv_transformation: list, x: np.ndarray): r""" Transform the coefficients of ODE from one variable to another evaluated on mesh points. Given a :math:`K`-th differentiable transformation function :math:`g(x)` and a linear ODE of :math:`K`-th order on independent variable :math:`x` .. math:: \sum_{k=1}^{K} a_k(x) \frac{d^k y(x)}{d x^k}. This transforms it into a new coordinate system :math:`g(x) =: r` .. math:: \sum_{j=1}^K b_j(r) \frac{d^j y(r)}{d r^j}, where :math:`b_j(r) = \sum_{k=1}^K a_k(g^{-1}(r)) B_{k, j}({g^{-1}}^\prime(g^{-1}(r)), \cdots, {g^{-1}}^{k - j + 1}(g^{-1}(r)))`. Parameters ---------- coeffs : list[callable or number] or ndarray Coefficients :math:`a_k` of each term :math:`\frac{d^k y(x}{d x^k}` ordered from 0 to K. Either a list of callable functions :math:`a_k(x)` that depends on :math:`x` or array of constants :math:`\{a_k\}_{k=0}^{K}`. deriv_transformation : list[callable] List of functions for compute transformation derivatives from 1 to :math:`K`. x : ndarray Points from the original domain that is to be transformed. Returns ------- ndarray((K+1, N)) Coefficients :math:`b_j(r)` of the new ODE with respect to transformed variable :math:`r`. """ # `derivs` has shape (K, N), calculate d^j g/ dr^j derivs = np.array([dev(x) for dev in deriv_transformation], dtype=np.float64) total = len(coeffs) # Should be K+1, K is the order of the ODE # coeff_a_mtr has shape (K+1, N) coeff_a_mtr = _evaluate_coeffs_on_points(x, coeffs) # a_k(x) coeff_b = np.zeros((total, x.size), dtype=float) # The first term doesn't contain a derivative so no transformation is required: coeff_b[0] += coeff_a_mtr[0] # construct 1 - 3 directly with vectorization (faster) if total > 1: coeff_b[1] += coeff_a_mtr[1] * derivs[0] if total > 2: coeff_b[1] += coeff_a_mtr[2] * derivs[1] coeff_b[2] += coeff_a_mtr[2] * derivs[0]**2 if total > 3: coeff_b[1] += coeff_a_mtr[3] * derivs[2] coeff_b[2] += coeff_a_mtr[3] * 3 * derivs[0] * derivs[1] coeff_b[3] += coeff_a_mtr[3] * derivs[0]**3 # construct 4th order and onwards without vectorization (slower) # formula is: d^k f / dr^k = \sum_{j=1}^{k} Bell_{k, j}(...) (d^jf / dx^j) if total > 4: # Go Through each Pt for i_pt in range(len(x)): # Go through each order from 4 to K + 1 for j in range(4, total): # Go through the sum to calculate Bell's polynomial for k in range(j, total): all_derivs_at_pt = derivs[:, i_pt] coeff_b[j, i_pt] += (float(bell(k, j, all_derivs_at_pt)) * coeff_a_mtr[k, i_pt]) return coeff_b