def handle_sum_or_prod(func, name): val = convert_mp(func.mp()) iter_var = convert_expr(func.subeq().equality().expr(0)) start = convert_expr(func.subeq().equality().expr(1)) if func.supexpr().expr(): # ^{expr} end = convert_expr(func.supexpr().expr()) else: # ^atom end = convert_atom(func.supexpr().atom()) if name == "summation": return sympy.Sum(val, (iter_var, start, end)) elif name == "product": return sympy.Product(val, (iter_var, start, end))
def dtft(xn): """离散时间傅里叶变换 :Parameters: - xn: 离散非周期序列,序列只能有1个自变量 :Returns: 频谱密度函数(周期连续信号),自变量为Omega(用W表示) """ raise Exception("Can't sum from -oo to oo") n = tuple(xn.free_symbols)[0] W = sy.symbols('W') r = (n, -sy.oo, sy.oo) xW = sy.Sum(xn * sy.exp(-sy.I * W * n), r) return xW
def _initialize_functions(self): """ Build up the nDimensionssymbolic definitions """ # Parameters nDimensions= self.constants[self.nDimensions] self.position = sp.Matrix([sp.symbols("r_" + str(i)) for i in range(nDimensions)]) self.r_shift = sp.Matrix([sp.symbols("r_shift" + str(i)) for i in range(nDimensions)]) self.V_off = sp.Matrix([sp.symbols("V_off_" + str(i)) for i in range(nDimensions)]) self.k = sp.Matrix([sp.symbols("k_" + str(i)) for i in range(nDimensions)]) # Function self.V_dim = 0.5 * sp.matrix_multiply_elementwise(self.k, ( (self.position - self.r_shift).applyfunc(lambda x: x ** 2))) # +self.Voff self.V_functional = sp.Sum(self.V_dim[self.i, 0], (self.i, 0, self.nDimensions - 1))
def remove_images(expr, var, dt, m1=0, m2=0): if m2 == 0 and isinstance(m1, tuple) and len(m1) == 2: # Perhaps should warn that this might be deprecated? m1, m2 = m1 remove_all = m1 == 0 and m2 == 0 const, expr1 = factor_const(expr, var) result = sym.S.Zero terms = expr1.as_ordered_terms() if len(terms) > 1: for term in expr1.as_ordered_terms(): result += remove_images(term, var, dt, m1, m2) return const * result if not isinstance(expr1, sym.Sum): return expr sumsym = expr1.args[1].args[0] def query(expr): return expr.is_Add and expr.has(var) and expr.has(sumsym) def value(expr): if not expr.is_Add: return expr if not expr.is_polynomial(var) and not expr.as_poly(var).is_linear: return expr expr = expr.expand() a = expr.coeff(var, 1) b = expr.coeff(var, 0) if a == 0: return expr if b / a != -sumsym / dt: return expr return a * var expr1 = expr1.replace(query, value) if remove_all: return const * expr1.args[0] return const * sym.Sum(expr1.args[0], (sumsym, m1, m2))
def test_energy_weighted_leastnorm(): # Test energy model of weighted least norm. omega = -5. for order in range(0, 3): # Test weighted model. for weight in np.linspace(0.01, 1., 3): n_elec = sp.symbols("n_elec") _, dict_energy, _, expr, alpha, beta = make_symbolic_least_norm_model( omega, order, weight) alpha = sp.Sum(alpha, (n_elec, 2, 13)).doit() beta = sp.Sum(beta, (n_elec, 2, 13)).doit() weighted = LeastNormGlobalTool(dict_energy, omega, order, weight, eps=1e-9) actual = [weighted.energy(n_elec) for n_elec in range(1, 20)] desired = [ expr[0].subs([("n_elec", n_elec), ('alpha', alpha), ('beta', beta)]).evalf() for n_elec in range(1, 20) ] assert_almost_equal(actual, desired, decimal=4)
def generic_function(val, deriv=0): """ Returns specified derivative of a polynomial series. To be used in the place of functions for specification of boundary conditions. Parameters ---------- val : Sympy symbol The variable of the function: x_b should be used. deriv : int The order of the derivative. Default is zero. """ x_poly = sp.symbols('x_poly') polynomial = sp.Sum(a[n] * x_poly**n, (n, 0, n_max)) return sp.diff(polynomial, x_poly, deriv).subs(x_poly, val)
def parametrization( # pylint: disable=too-many-arguments i: int, s: sp.Symbol, pole_position: sp.IndexedBase, pole_width: sp.IndexedBase, residue_constant: sp.IndexedBase, beta_constant: sp.IndexedBase, n_poles: Union[int, sp.Symbol], pole_id: Union[int, sp.Symbol], ) -> sp.Expr: beta = beta_constant[pole_id] gamma = residue_constant[pole_id, i] mass = pole_position[pole_id] width = pole_width[pole_id, i] parametrization = beta * gamma * mass * width / (mass**2 - s) return sp.Sum(parametrization, (pole_id, 1, n_poles))
def _initialize_functions(self): """ _initialize_functions converts the symbolic mathematics of sympy to a matrix representation that is compatible with multi-dimentionality. """ self.position = sp.Matrix([ sp.symbols("r_" + str(i)) for i in range(self.constants[self.nDimensions]) ]) self.wave_potentials = sp.Matrix([ sp.symbols("V_" + str(i)) for i in range(self.constants[self.nWavePotentials]) ]) # Function self.V_functional = sp.Sum(self.wave_potentials[self.i, 0], (self.i, 0, self.nWavePotentials - 1))
def process_var(self, subscripts, vars_, var): subs_ = [subscripts[i] for i in self.subscripts.keys() if f'_{i}' in var] if var in self.totals.keys(): total_subs = [i for i in self.subscripts.keys() if f'_{i}' in var] other_subs = [i for i in self.subscripts.keys() if f'_{i}' in var] diff = [i for i in total_subs if i not in other_subs] for d in diff: m = self.subscripts[d]['count'] var = sp.Sum(var, (d, 1, m)) var = vars_[var[0]] return var
def idfs(xk, N:int, r:tuple=()): """离散傅里叶级数逆运算 :Parameters: - xk: 离散周期频谱序列,序列只能有1个自变量 - N: 序列最小数字周期 - r: 序列一个周期的范围 :Returns: 原信号序列(离散周期序列),序列自变量为n """ k = tuple(xk.free_symbols)[0] n = sy.symbols('n') if not r: r = (k, 0, N-1) W = sy.exp(sy.I * 2 * sy.pi * k * n / N) xn = sy.Sum(xk * W, r) / N return xn
def _calculate_z_transform(self): z = sympy.symbols('z') n = sympy.symbols('n') try: f = sympy.parsing.sympy_parser.parse_expr(self.line_exp_sample.text()) F = sympy.Sum(f * z ** (-n), (n, 0, sympy.oo)).doit(noconds=True) while isinstance(F, (sympy.Piecewise, sympy.functions.elementary.piecewise.ExprCondPair)): F = F.args[0] except (ValueError, TypeError, SyntaxError) as ex: error_msg = f'ValueError: invalid expression' print(ex, error_msg) QtWidgets.QMessageBox.about(self, 'Error message', error_msg) return -1 self.label_exp_state_val.setText(str(F))
def parametrization( # pylint: disable=too-many-arguments i: int, j: int, s: sp.Symbol, pole_position: sp.IndexedBase, pole_width: sp.IndexedBase, residue_constant: sp.IndexedBase, n_poles: Union[int, sp.Symbol], pole_id: Union[int, sp.Symbol], ) -> sp.Expr: def residue_function(pole_id: int, i: int) -> sp.Expr: return residue_constant[pole_id, i] * sp.sqrt( pole_position[pole_id] * pole_width[pole_id, i]) g_i = residue_function(pole_id, i) g_j = residue_function(pole_id, j) parametrization = (g_i * g_j) / (pole_position[pole_id]**2 - s) return sp.Sum(parametrization, (pole_id, 1, n_poles))
def test_piecewise_simplification(): d2d3_11 = (c * d * TP(x, sp.Piecewise( (x, sp.Eq(k, 0)), (0, True))) + c * d * TP(sp.Piecewise( (x, sp.Eq(k, 0)), (0, True)), x) + d * TP(1, sp.Piecewise( (x, sp.Eq(k, 0)), (0, True))) + d * TP(sp.Piecewise((x, sp.Eq(k, 0)), (0, True)), 1)) assert texpand(d2d3_11) == 0 expr = sp.Sum( sp.Piecewise( (c * TP(xy**k, y * xy**(k - q - 1) * y) + TP(y * xy**(k - 1), y * xy**(k - q - 1) * y), sp.Eq(q, 0)), (0, True)), (q, 0, k - 1)) assert texpand(expr) == 0
def manual_y_computation(weighting_seq, input_signal, T): logging.info('ŷ(t) = ĝ(t) * u(t)') g_ = sy.IndexedBase('ĝ') u = sy.IndexedBase('u') y_ = sy.Sum(g_[k] * u[T - k], (k, 0, T)) logging.info(f'ŷ({T}) = {y_}') inter_step = lambda weighting_seq: (y_.doit().subs([ (u[i], u_value) for i, u_value in enumerate(input_signal) ]).subs([(g_[i], g_value) for i, g_value in enumerate(weighting_seq)])) g_sym = inter_step(map(lambda x: sy.symbols(str(x)), weighting_seq)) logging.info(f'ŷ({T}) = {g_sym}') g_val = inter_step(weighting_seq) logging.info(f'ŷ({T}) = {g_val}') return g_val
def state_forced_response_discrete(a, b, u, show_steps=True): '''Computes the forced response of the state variables based on the input u. x_homo = state_trans_matrix * initial_cond Params: a (list or sympy Matrix): A list of lists for the rows of the A matrix of the system b (list or sympy Matrix): A list of lists for the b matrix u (list,sympy Matrix, or a constant): the input to the system show_steps (bool, True): Pretty prints the steps of the computation Returns: sympy Matrix: the forced response ''' a = sy.Matrix(a) b = sy.Matrix(b) # T is for tau k, i = sy.symbols('k i') state_trans_neg = state_transition_matrix_discrete(a, k - 1 - i, show_steps=False) state_trans = state_transition_matrix_discrete(a, k, show_steps=False) # This is in case the u param is dependant on k, if it is we need to make it # into an i for the summation below try: u = u.replace(k, i) except: pass unevaluated_conv_sum = sy.Sum(state_trans_neg * b * u, (i, 0, k - 1)) forced_resp = unevaluated_conv_sum.doit() # sympy returns None if the answer is zero if not forced_resp: forced_resp = sy.zeros(state_trans.shape[0]) if show_steps: print("Finding the forced response") display_steps(a, "A matrix") display_steps(b, "B matrix") display_steps(state_trans, "State transition matrix: ") display_steps(state_trans_neg, "Negative state trans matrix") display_steps(unevaluated_conv_sum, "The convolution summation") display_steps(forced_resp, "Forced response is ") return forced_resp
def __init__(self, V_is: t.List[_potential1DCls], s: float = 1.0, Eoff_i: t.List[float] = None, T: float = 298): """ :param V_is: :param s: :param Eoff_i: """ super().__init__(V_is=V_is, s=s, Eoff_i=Eoff_i) #Sympy Implementation Eoffis = {"Eoff_" + str(i): Eoff_i[i] for i in range(self.nStates)} self.statePotentials = { "state_" + str(j): V_is[j] for j in range(self.nStates) } self.states = sp.Matrix([ sp.symbols(j) - sp.symbols(k) for j, k in zip(self.statePotentials, Eoffis) ]) self.constants = { **{ state: value.V for state, value in self.statePotentials.items() }, **Eoffis, **{ "s": s, self.T: T, self.N: self.nStates } } self.V_orig = -1 / (self.beta * self.s_s) * sp.log( sp.Sum(sp.exp(-self.beta * self.s_s * (self.states[self.i, 0])), (self.i, 0, self.N - 1))) self.V = self.V_orig.subs(self.constants) self.dVdpos = sp.diff(self.V, self.position)
def func(self, expr, n, z): if not isinstance(expr, AppliedUndef): self.error('Expecting function') scale, shift = scale_shift(expr.args[0], n) # Convert v(n) to V(z), etc. name = expr.func.__name__ func = sym.Function(name[0].upper() + name[1:]) if not scale.is_constant(): self.error('Cannot determine if time-expansion or decimation') if scale == 1: result = func(z) if shift != 0: result = result * z ** shift return result if scale.is_integer: # Down-sampling produces aliasing # Sum(X(z**(1 / M) * exp(-j * 2 * pi * m / M), (m, 0, M - 1)) / M) # Down-sampling is not shift invariant so z-transform # is an approximation. m = self.dummy_var(expr, 'm', level=0, real=True) expr = func(z**(1 / scale) * sym.exp(-sym.I * 2 * sym.pi * m / scale)) return sym.Sum(expr, (m, 0, scale - 1)) / scale if not scale.is_rational: self.error('Cannot handle arbitrary scaling') if scale.p != 1: self.error('Cannot handle non-integer time-expansion') result = func(z ** scale.q) if shift != 0: result = result * z ** shift return result
def _initialize_functions(self): # Parameters nDim = self.constants[self.nDim] self.position = sp.Matrix( [sp.symbols("pos_" + str(i)) for i in range(nDim)]) self.multiplicity = sp.Matrix( [sp.symbols("mult_" + str(i)) for i in range(nDim)]) self.phase_shift = sp.Matrix( [sp.symbols("phase_" + str(i)) for i in range(nDim)]) self.amplitude = sp.Matrix( [sp.symbols("amp_" + str(i)) for i in range(nDim)]) self.yOffset = sp.Matrix( [sp.symbols("yOff_" + str(i)) for i in range(nDim)]) #Function self.V_dim = sp.matrix_multiply_elementwise( self.amplitude, (sp.matrix_multiply_elementwise( (self.position + self.phase_shift), self.multiplicity)).applyfunc(sp.cos)) + self.yOffset self.V_orig = sp.Sum(self.V_dim[self.i, 0], (self.i, 0, self.nDim - 1))
def duration(self) -> ExpressionScalar: step_size = self._loop_range.step.sympified_expression loop_index = sympy.symbols(self._loop_index) sum_index = sympy.symbols(self._loop_index) # replace loop_index with sum_index dependable expression body_duration = self.body.duration.sympified_expression.subs({loop_index: self._loop_range.start.sympified_expression + sum_index*step_size}) # number of sum contributions step_count = sympy.ceiling((self._loop_range.stop.sympified_expression-self._loop_range.start.sympified_expression) / step_size) sum_start = 0 sum_stop = sum_start + (sympy.functions.Max(step_count, 1) - 1) # expression used if step_count >= 0 finite_duration_expression = sympy.Sum(body_duration, (sum_index, sum_start, sum_stop)) duration_expression = sympy.Piecewise((0, step_count <= 0), (finite_duration_expression, True)) return ExpressionScalar(duration_expression)
def match_transform(v): if not isinstance(v, sp.Add): return None terms = v.args coeffs_to_terms = split_coefficients_into_dict_keys(terms) was_update = False new_terms = [] for (coeff, terms) in coeffs_to_terms.items(): sums, not_sums = separate_sums(terms) for i1 in range(len(sums)): for i2 in range(len(sums)): if i1 == i2: continue sum1 = sums[i1] sum2 = sums[i2] func1 = sum1.function func2 = sum2.function var1 = sum1.limits[0][0] var2 = sum2.limits[0][0] if func1.subs(var1, var2 + 1) == func2: # match! was_update = True new_func = func1.subs(var1, var2) new_limits = (var2, sum2.limits[0][1] + 1, sum2.limits[0][2] + 1) sums[i2] = sp.Sum(new_func, new_limits) new_terms.extend([coeff * term for term in not_sums + sums]) if was_update: return sp.Add(*new_terms) else: return None
def setWeierstrassFunction(self): #Example of how to define and set a problem using the parameter class. self.p_initialPopulation = 50 self.p_populationCap = 50 self.p_specimensPruned = 45 self.p_mutationRate = 0.001 self.p_basisFunction = (a * x) self.p_targetFitness = 0 self.p_problemVars = [x1] self.p_problemDomain = [(-2, 2)] self.p_objectiveFunction = sp.Sum(0.9**n * sp.cos(7**n * np.pi * x1), (n, self.p_problemDomain[0][0], self.p_problemDomain[0][1])).doit() self.p_constraints = [] self.p_fitnessSamples = 101 self.p_maxOrder = 3 self.p_maxStages = 4 self.p_fitnessFunc = fixedDomainCorrelationAndMeanSquareFitness self.p_reproductionMethod = cellularMethodVariableTermMutation
def _initialize_functions(self): # for sympy Sympy Updates - Check!: self.statePotentials = {"state_" + str(j): self.V_is[j] for j in range(self.constants[self.nStates])} Eoffis = {"Eoff_" + str(i): self.Eoff_i[i] for i in range(self.constants[self.nStates])} sis = {"s_" + str(i): self.s_i[i] for i in range(self.constants[self.nStates])} lamis = {"lam_" + str(i): self.lam_i[i] for i in range(self.constants[self.nStates])} keys = zip(sorted(self.statePotentials.keys()), sorted(Eoffis.keys()), sorted(sis.keys())) self.states = sp.Matrix([sp.symbols(l) * (sp.symbols(j) - sp.symbols(k)) for j, k, l in keys]) self.constants.update( {**{state: value.V for state, value in self.statePotentials.items()}, **Eoffis, **sis, **lamis}) inner_log = sp.Sum(sp.Matrix(list(lamis.keys()))[self.i, 0] * sp.exp(-self.beta * self.states[self.i, 0]), (self.i, 0, self.nStates - 1)) self.V_functional = -1 / (self.beta * self.sis[0, 0]) * sp.log(inner_log) self._update_functions() # also make sure that states are up to work: [V._update_functions() for V in self.V_is] self.ene = self._calculate_energies_singlePos_overwrite self.force = self._calculate_dvdpos_singlePos_overwrite
def _initialize_functions(self): """ _initialize_functions converts the symbolic mathematics of sympy to a matrix representation that is compatible with multi-dimentionality. """ # Parameters nDimensions = self.constants[self.nDimensions] self.position = sp.Matrix( [sp.symbols("r_" + str(i)) for i in range(nDimensions)]) self.r_shift = sp.Matrix( [sp.symbols("r_shift" + str(i)) for i in range(nDimensions)]) self.V_off = sp.Matrix( [sp.symbols("V_off_" + str(i)) for i in range(nDimensions)]) self.k = sp.Matrix( [sp.symbols("k_" + str(i)) for i in range(nDimensions)]) # Function self.V_dim = 0.5 * sp.matrix_multiply_elementwise( self.k, ((self.position - self.r_shift).applyfunc(lambda x: x**2))) # +self.Voff self.V_functional = sp.Sum(self.V_dim[self.i, 0], (self.i, 0, self.nDimensions - 1))
def __init__(self, wavePotentials): ''' initializes torsions Potential ''' self.constants = { **{ "wave_" + str(key): wave.V for key, wave in enumerate(wavePotentials) }, **{ self.N: len(wavePotentials) - 1 } } self.wavePotentials = sp.Matrix( [sp.symbols("wave_" + str(i)) for i in range(len(wavePotentials))]) self.V_orig = sp.Sum(self.wavePotentials[self.i, 0], (self.i, 0, self.N)) self.V = self.V_orig.subs(self.constants).subs(self.N, len(wavePotentials)) super().__init__()
def handle_sum_or_prod(func, name): val = convert_mp(func.mp()) iter_var = None start = None if func.subeq(): # e.g. _{i=0} iter_var = convert_expr(func.subeq().equality().expr(0)) start = convert_expr(func.subeq().equality().expr(1)) elif func.subexpr().expr(): # _{expr} iter_var = convert_expr(func.subexpr().expr()) else: # _atom iter_var = convert_atom(func.subexpr().atom()) if func.supexpr(): if func.supexpr().expr(): # ^{expr} end = convert_expr(func.supexpr().expr()) else: # ^atom end = convert_atom(func.supexpr().atom()) if name == "summation": if start is not None: return sympy.Sum(val, (iter_var, start, end)) else: return sympy.Function('sum_{' + str(iter_var) + '}')(val) elif name == "product": return sympy.Product(val, (iter_var, start, end))
def calculator(pool, var_node): """ Symbolically sums the expression of the node. :return: a lambda expression that substitutes the given lower- and upper bound in the sum """ variable, node_id = var_node expression = pool.get_node(node_id).expression variables = {str(v): v for v in expression.free_symbols} if len(variables) == 0: return lambda lb, ub: (ub - lb + 1) * float(expression) v = variables[variable] if variable in variables else sympy.S(variable) # TODO add caching again try: # expression = sympy.expand(expression) # print("Value at r=10 is {}".format(expression.subs({"r": 10}))) result = sympy.Sum(expression, (v, self.lb, self.ub)).doit() # print("Symbolic sum of {} = {}".format(expression, result)) return lambda lb, ub: result.subs({self.lb: lb, self.ub: ub}) except sympy.BasePolynomialError as e: print("Problem trying to sum the expression {} for variable {}" .format(expression, v)) raise e
def integral(self) -> Dict[ChannelID, ExpressionScalar]: step_size = self._loop_range.step.sympified_expression loop_index = sympy.symbols(self._loop_index) sum_index = sympy.symbols(self._loop_index) body_integrals = self.body.integral body_integrals = { c: body_integrals[c].sympified_expression.subs( {loop_index: self._loop_range.start.sympified_expression + sum_index*step_size} ) for c in body_integrals } # number of sum contributions step_count = sympy.ceiling((self._loop_range.stop.sympified_expression-self._loop_range.start.sympified_expression) / step_size) sum_start = 0 sum_stop = sum_start + (sympy.functions.Max(step_count, 1) - 1) for c in body_integrals: channel_integral_expr = sympy.Sum(body_integrals[c], (sum_index, sum_start, sum_stop)) body_integrals[c] = ExpressionScalar(channel_integral_expr) return body_integrals
def test_sum(): sum = sympy.Sum(k, (k, 1, 100)) expanded_sum = sum.doit() print(sum) print(expanded_sum) x = pystencils.fields('x: float32[1d]') assignments = pystencils.AssignmentCollection({x.center(): sum}) ast = pystencils.create_kernel(assignments) code = str(pystencils.show_code(ast)) kernel = ast.compile() print(code) assert 'double sum' in code array = np.zeros((10, ), np.float32) kernel(x=array) assert np.allclose(array, int(expanded_sum) * np.ones_like(array))
class envelopedPotential(_potentialNDCls): """ This implementation of exponential Coupling for EDS is a more numeric robust and variable implementation, it allows N states. Therefore the computation of energies and the deviation is not symbolic. Here N-states are coupled by the log-sum-exp resulting in a new reference state $V_R$, $V_R = -1/{\beta} * \ln(\sum_i^Ne^(-\beta*s*(V_i-E^R_i)))$ This potential coupling is for example used in EDS. """ name = "Enveloping Potential" T, kb, position = sp.symbols("T kb r") beta = 1 / (kb * T) Vis = sp.Matrix(["V_i"]) Eoffis = sp.Matrix(["Eoff_i"]) sis = sp.Matrix(["s_i"]) i, nStates = sp.symbols("i N") V_functional = -1 / (beta * sis[0, 0]) * sp.log( sp.Sum(sp.exp(-beta * sis[i, 0] * (Vis[i, 0] - Eoffis[i, 0])), (i, 0, nStates))) def __init__(self, V_is: t.List[_potentialNDCls] = ( harmonicOscillatorPotential(nDimensions=2), harmonicOscillatorPotential(r_shift=[3,3], nDimensions=2)), s: float = 1.0, eoff: t.List[float] = None, T: float = 1, kb: float = 1): """ __init__ This function constructs a enveloped potential, enveloping all given states. Parameters ---------- V_is: List[_potential1DCls], optional The states(potential classes) to be enveloped (default: [harmonicOscillatorPotential(), harmonicOscillatorPotential(x_shift=3)]) s: float, optional the smoothing parameter, lowering the barriers between the states eoff: List[float], optional the energy offsets of the individual states in the reference potential. These can be used to allow a more uniform sampling. (default: seta ll to 0) T: float, optional the temperature of the reference state (default: 1 = T) kb: float, optional the boltzman constant (default: 1 = kb) """ self.constants = {self.T: T, self.kb: kb} nStates = len(V_is) self._Eoff_i = [0 for x in range(nStates)] self._s = [0 for x in range(nStates)] self._V_is = [0 for x in range(nStates)] # for calculate implementations self.V_is = V_is self.s_i = s self.Eoff_i = eoff super().__init__(nDimensions=V_is[0].constants[V_is[0].nDimensions], nStates=len(V_is)) def _initialize_functions(self): """ build the symbolic functionality. """ # for sympy Sympy Updates - Check!: self.statePotentials = {"state_" + str(j): self.V_is[j] for j in range(self.constants[self.nStates])} Eoffis = {"Eoff_" + str(i): self.Eoff_i[i] for i in range(self.constants[self.nStates])} sis = {"s_" + str(i): self.s_i[i] for i in range(self.constants[self.nStates])} keys = zip(sorted(self.statePotentials.keys()), sorted(Eoffis.keys()), sorted(sis.keys())) self.states = sp.Matrix([sp.symbols(l) * (sp.symbols(j) - sp.symbols(k)) for j, k, l in keys]) self.constants.update({**{state: value.V for state, value in self.statePotentials.items()}, **Eoffis, **sis}) self.V_functional = -1 / (self.beta * self.sis[0, 0]) * sp.log( sp.Sum(sp.exp(-self.beta * self.states[self.i, 0]), (self.i, 0, self.nStates - 1))) self._update_functions() # also make sure that states are up to work: [V._update_functions() for V in self.V_is] if (all([self.s_i[0] == s for s in self.s_i[1:]])): self.ene = self._calculate_energies_singlePos_overwrite_oneS else: self.ene = self._calculate_energies_singlePos_overwrite_multiS self.force = self._calculate_dvdpos_singlePos_overwrite @property def V_is(self) -> t.List[_potentialNDCls]: """ V_is are the state potential classes enveloped by the reference state. Returns ------- V_is: t.List[_potential1DCls] """ return self._V_is @V_is.setter def V_is(self, V_is: t.List[_potentialNDCls]): if (isinstance(V_is, Iterable) and all([isinstance(Vi, _potentialNDCls) for Vi in V_is])): self._V_is = V_is self.constants.update({self.nStates: len(V_is)}) else: raise IOError("Please give the enveloped potential for V_is only 1D-Potential classes in a list.") def set_Eoff(self, Eoff: Union[Number, Iterable[Number]]): """ This function is setting the Energy offsets of the states enveloped by the reference state. Parameters ---------- Eoff: Union[Number, Iterable[Number]] """ self.Eoff_i = Eoff @property def Eoff(self) -> t.List[Number]: """ The Energy offsets are used to bias the single states in the reference potential by a constant offset. Therefore each state of the enveloping potential has its own energy offset. Returns ------- Eoff:t.List[Number] """ return self.Eoff_i @Eoff.setter def Eoff(self, Eoff: Union[Number, Iterable[Number], None]): self.Eoff_i = Eoff @property def Eoff_i(self) -> t.List[Number]: """ The Energy offsets are used to bias the single states in the reference potential by a constant offset. Therefore each state of the enveloping potential has its own energy offset. Returns ------- Eoff:t.List[Number] """ return self._Eoff_i @Eoff_i.setter def Eoff_i(self, Eoff: Union[Number, Iterable[Number], None]): if (isinstance(Eoff, type(None))): self._Eoff_i = [0.0 for state in range(self.constants[self.nStates])] Eoffis = {"Eoff_" + str(i): self.Eoff_i[i] for i in range(self.constants[self.nStates])} self.constants.update({**Eoffis}) elif (len(Eoff) == self.constants[self.nStates]): self._Eoff_i = Eoff Eoffis = {"Eoff_" + str(i): self.Eoff_i[i] for i in range(self.constants[self.nStates])} self.constants.update({**Eoffis}) else: raise IOError( "Energy offset Vector and state potentials don't have the same length!\n states in Eoff " + str( len(Eoff)) + "\t states in Vi" + str(len(self.V_is))) def set_s(self, s: Union[Number, Iterable[Number]]): """ set_s is a function used to set an smoothing parameter. Parameters ---------- s:Union[Number, Iterable[Number]] Returns ------- """ self.s_i = s @property def s(self) -> t.List[Number]: return self.s_i @s.setter def s(self, s: Union[Number, Iterable[Number]]): self.s_i = s @property def s_i(self) -> t.List[Number]: return self._s @s_i.setter def s_i(self, s: Union[Number, Iterable[Number]]): if (isinstance(s, Number)): self._s = [s for x in range(self.constants[self.nStates])] sis = {"s_" + str(i): self.s_i[i] for i in range(self.constants[self.nStates])} self.constants.update({**sis}) elif (len(s) == self.constants[self.nStates]): self._s = s sis = {"s_" + str(i): self.s_i[i] for i in range(self.constants[self.nStates])} self.constants.update({**sis}) else: raise IOError("s Vector/Number and state potentials don't have the same length!\n states in s " + str( len(s)) + "\t states in Vi" + str(len(self.V_is))) self._update_functions() def _calculate_energies_singlePos_overwrite_multiS(self, positions) -> np.array: sum_prefactors, _ = self._logsumexp_calc_gromos(positions) beta = self.constants[self.T] * self.constants[self.kb] # kT - *self.constants[self.T] Vr = (-1 / (beta)) * sum_prefactors return np.squeeze(Vr) def _calculate_energies_singlePos_overwrite_oneS(self, positions) -> np.array: sum_prefactors, _ = self._logsumexp_calc(positions) beta = self.constants[self.T] * self.constants[self.kb] Vr = (-1 / (beta * self.s_i[0])) * sum_prefactors return np.squeeze(Vr) def _calculate_dvdpos_singlePos_overwrite(self, positions: (t.Iterable[float])) -> np.array: """ Parameters ---------- positions Returns ------- """ positions = np.array(positions, ndmin=2) # print("Pos: ", position) V_R_part, V_Is_ene = self._logsumexp_calc_gromos(positions) V_R_part = np.array(V_R_part, ndmin=2).T # print("V_R_part: ", V_R_part.shape, V_R_part) # print("V_I_ene: ",V_Is_ene.shape, V_Is_ene) V_Is_dhdpos = np.array([-statePot.force(positions) for statePot in self.V_is], ndmin=1).T # print("V_I_force: ",V_Is_dhdpos.shape, V_Is_dhdpos) adapt = np.concatenate([V_R_part for s in range(self.constants[self.nStates])], axis=1) # print("ADAPT: ",adapt.shape, adapt) scaling = np.exp(V_Is_ene - adapt) # print("scaling: ", scaling.shape, scaling) dVdpos_state = np.multiply(scaling, V_Is_dhdpos) # np.array([(ene/V_R_part) * force for ene, force in zip(V_Is_ene, V_Is_dhdpos)]) # print("state_contributions: ",dVdpos_state.shape, dVdpos_state) dVdpos = np.sum(dVdpos_state, axis=1) # print("forces: ",dVdpos.shape, dVdpos) return np.squeeze(dVdpos) def _logsumexp_calc(self, position): prefactors = [] beta = self.constants[self.T] * self.constants[self.kb] for state in range(self.constants[self.nStates]): prefactor = np.array(-beta * self.s_i[state] * (self.V_is[state].ene(position) - self.Eoff_i[state]), ndmin=1).T prefactors.append(prefactor) prefactors = np.array(prefactors, ndmin=2).T from scipy.special import logsumexp # print("Prefactors", prefactors) sum_prefactors = logsumexp(prefactors, axis=1) # print("logexpsum: ", np.squeeze(sum_prefactors)) return np.squeeze(sum_prefactors), np.array(prefactors, ndmin=2).T def _logsumexp_calc_gromos(self, position): """ code from gromos: Parameters ---------- position Returns ------- """ prefactors = [] beta = self.constants[self.T] * self.constants[self.kb] # kT - *self.constants[self.T] partA = np.array(-beta * self.s_i[0] * (self.V_is[0].ene(position) - self.Eoff_i[0]), ndmin=1) partB = np.array(-beta * self.s_i[1] * (self.V_is[1].ene(position) - self.Eoff_i[1]), ndmin=1) partAB = np.array([partA, partB]).T log_prefac = 1 + np.exp(np.min(partAB, axis=1) - np.max(partAB, axis=1)) sum_prefactors = np.max(partAB, axis=1) + np.log(log_prefac) prefactors.append(partA) prefactors.append(partB) # more than two states! for state in range(2, self.constants[self.nStates]): partN = np.array(-beta * self.s_i[state] * (self.V_is[state].ene(position) - self.Eoff_i[state]), ndmin=1) prefactors.append(partN) sum_prefactors = np.max([sum_prefactors, partN], axis=1) + np.log(1 + np.exp( np.min([sum_prefactors, partN], axis=1) - np.max([sum_prefactors, partN], axis=1))) # print("prefactors: ", sum_prefactors) return sum_prefactors, np.array(prefactors, ndmin=2).T
class sumPotentials(_potentialNDCls): """ Adds n different potentials. For adding up wavepotentials, we recommend using the addedwavePotential class. """ name: str = "Summed Potential" position = sp.symbols("r") potentials: sp.Matrix = sp.Matrix([sp.symbols("V_x")]) nPotentials = sp.symbols("N") i = sp.symbols("i", cls=sp.Idx) V_functional = sp.Sum(potentials[i, 0], (i, 0, nPotentials)) def __init__(self, potentials: t.List[_potentialNDCls] = (harmonicOscillatorPotential(), harmonicOscillatorPotential(r_shift=[1,1,1], nDimensions=3))): """ __init__ This is the Constructor of an summed Potentials Parameters ---------- potentials: List[_potential2DCls], optional it uses the 2D potential class to generate its potential, default to (wavePotential(), wavePotential(multiplicity=[3, 3])) """ if(all([potentials[0].constants[V.nDimensions] == V.constants[V.nDimensions] for V in potentials])): nDim = potentials[0].constants[potentials[0].nDimensions] else: raise ValueError("The potentials don't share the same dimensionality!\n\t"+str([V.constants[V.nDimensions] for V in potentials])) self.constants = {self.nPotentials: len(potentials)} self.constants.update({"V_" + str(i): potentials[i].V for i in range(len(potentials))}) super().__init__(nDimensions=nDim) def _initialize_functions(self): """ _initialize_functions converts the symbolic mathematics of sympy to a matrix representation that is compatible with multi-dimentionality. """ self.position = sp.Matrix([sp.symbols("r_" + str(i)) for i in range(self.constants[self.nDimensions])]) self.potentials = sp.Matrix( [sp.symbols("V_" + str(i)) for i in range(self.constants[self.nPotentials])]) # Function self.V_functional = sp.Sum(self.potentials[self.i, 0], (self.i, 0, self.nPotentials - 1)) def __str__(self) -> str: msg = self.__name__() + "\n" msg += "\tStates: " + str(self.constants[self.nStates]) + "\n" msg += "\tDimensions: " + str(self.nDimensions) + "\n" msg += "\n\tFunctional:\n " msg += "\t\tV:\t" + str(self.V_functional) + "\n" msg += "\t\tdVdpos:\t" + str(self.dVdpos_functional) + "\n" msg += "\n\tSimplified Function\n" msg += "\t\tV:\t" + str(self.V) + "\n" msg += "\t\tdVdpos:\t" + str(self.dVdpos) + "\n" msg += "\n" return msg # OVERRIDE def _update_functions(self): """ _update_functions calculates the current energy and derivative of the energy """ super()._update_functions() self.tmp_Vfunc = self._calculate_energies self.tmp_dVdpfunc = self._calculate_dVdpos