def copy(self, deep=False): """ Create a copy of the current species. If the kw argument 'deep' is True, then a deep copy will be made of the Molecule objects in self.molecule. For other complex attributes, a deep copy will always be made. """ from copy import deepcopy cython.declare(other=Species) other = Species.__new__(Species) other.index = self.index other.label = self.label other.thermo = deepcopy(self.thermo) other.molecule = [] for mol in self.molecule: other.molecule.append(mol.copy(deep=deep)) other.conformer = deepcopy(self.conformer) other.transportData = deepcopy(self.transportData) other.molecularWeight = deepcopy(self.molecularWeight) other.energyTransferModel = deepcopy(self.energyTransferModel) other.reactive = self.reactive other.props = deepcopy(self.props) return other
def generate_minimum_resonance_isomer(mol): """ Select the resonance isomer that is isomorphic to the parameter isomer, with the lowest unpaired electrons descriptor. First, we generate all isomorphic resonance isomers. Next, we return the candidate with the lowest unpaired electrons metric. The metric is a sorted list with indices of the atoms that bear an unpaired electron """ cython.declare( candidates=list, sel=Molecule, cand=Molecule, metric_sel=list, metric_cand=list, ) candidates = resonance.generate_isomorphic_isomers(mol) sel = candidates[0] metric_sel = get_unpaired_electrons(sel) for cand in candidates[1:]: metric_cand = get_unpaired_electrons(cand) if metric_cand < metric_sel: sel = cand metric_sel = metric_cand return sel
def is_valid_combo(combo, mol, distances): """ Check if the combination of atom indices refers to atoms that are adjacent in the molecule. """ cython.declare( agglomerates=list, new_distances=list, orig_dist=dict, new_dist=dict, ) # compute shortest path between atoms agglomerates = agglomerate(combo) new_distances = compute_agglomerate_distance(agglomerates, mol) # combo is valid if the distance is equal to the parameter distance if len(distances) != len(new_distances): return False for orig_dist, new_dist in zip(distances, new_distances): # only compare the values of the dictionaries: if sorted(orig_dist.values()) != sorted(new_dist.values()): return False return True
def getDensityOfStates(self, Elist): """ Return the density of states at the specified energies `Elist` in J/mol above the ground state. For the cosine potential, the formula is .. math:: \\rho(E) = \\frac{2 q_\\mathrm{1f}}{\\pi^{3/2} V_0^{1/2}} \\mathcal{K}(E / V_0) \\hspace{20pt} E < V_0 and .. math:: \\rho(E) = \\frac{2 q_\\mathrm{1f}}{\\pi^{3/2} E^{1/2}} \\mathcal{K}(V_0 / E) \\hspace{20pt} E > V_0 where .. math:: q_\\mathrm{1f} = \\frac{\\pi^{1/2}}{\\sigma} \\left( \\frac{8 \\pi^2 I}{h^2} \\right)^{1/2} :math:`E` is energy, :math:`V_0` is barrier height, and :math:`\\mathcal{K}(x)` is the complete elliptic integral of the first kind. There is currently no functionality for using the Fourier series potential. """ cython.declare(rho=numpy.ndarray, q1f=cython.double, pre=cython.double, V0=cython.double, i=cython.int) rho = numpy.zeros_like(Elist) q1f = math.sqrt(8 * math.pi * math.pi * math.pi * self.inertia.value / constants.h / constants.h / constants.Na) / self.symmetry V0 = self.barrier.value pre = 2.0 * q1f / math.sqrt(math.pi * math.pi * math.pi * V0) # The following is only valid in the classical limit # Note that cellipk(1) = infinity, so we must skip that value for i in range(len(Elist)): if Elist[i] / V0 < 1: rho[i] = pre * cellipk(Elist[i] / V0) elif Elist[i] / V0 > 1: rho[i] = pre * math.sqrt(V0 / Elist[i]) * cellipk(V0 / Elist[i]) return rho
def getDensityOfStates(self, Elist): """ Return the value of the density of states in mol/J at the specified energies `Elist` in J/mol above the ground state. An active K-rotor is automatically included if there are no translational or external rotational modes. """ cython.declare(rho=numpy.ndarray, i=cython.int, E=cython.double) rho = numpy.zeros_like(Elist) # Active K-rotor # Only include this if there is no translation or rotation data translators = [mode for mode in self.modes if isinstance(mode, Translation)] rotors = [mode for mode in self.modes if isinstance(mode, RigidRotor)] if len(translators) == 0 and len(rotors) == 0: rho0 = numpy.zeros_like(Elist) for i, E in enumerate(Elist): if E > 0: rho0[i] = 1.0 / math.sqrt(1.0 * E) rho = convolve(rho, rho0, Elist) # Other non-vibrational modes for mode in self.modes: if not isinstance(mode, HarmonicOscillator): rho = convolve(rho, mode.getDensityOfStates(Elist), Elist) # Vibrational modes for mode in self.modes: if isinstance(mode, HarmonicOscillator): rho = mode.getDensityOfStates(Elist, rho) return rho * self.spinMultiplicity
def getDensityOfStates(self, Elist): """ Return the density of states at the specified energies `Elist` in J/mol above the ground state in mol/J. The formula is .. math:: \\rho(E) = \\frac{8 \\pi^2 I}{\\sigma h^2} for linear rotors and .. math:: \\rho(E) = \\frac{\\sqrt{\\pi}}{\\sigma} \\left( \\frac{8 \\pi^2}{h^2} \\right)^{3/2} \\sqrt{I_\\mathrm{A} I_\\mathrm{B} I_\\mathrm{C}} \\frac{E^{1/2}}{\\frac{1}{2}!} for nonlinear rotors. Above, :math:`E` is energy, :math:`\\sigma` is the symmetry number, :math:`I` is the moment of inertia, :math:`k_\\mathrm{B}` is the Boltzmann constant, and :math:`h` is the Planck constant. """ cython.declare(theta=cython.double, inertia=cython.double) if self.linear: inertia = self.inertia.value theta = constants.h * constants.h / (8 * constants.pi * constants.pi * inertia) * constants.Na return numpy.ones_like(Elist) / theta / self.symmetry else: theta = 1.0 for inertia in self.inertia.values: theta *= constants.h * constants.h / (8 * constants.pi * constants.pi * inertia) * constants.Na return 2.0 * numpy.sqrt(Elist / theta) / self.symmetry
def getEntropy(self, T): """ Return the contribution to the heat capacity due to hindered rotation in J/mol*K at the specified temperature `T` in K. For the cosine potential, this is calculated numerically from the partition function. For the Fourier series potential, we solve the corresponding 1D Schrodinger equation to obtain the energy levels of the rotor and utilize the expression .. math:: S^\\mathrm{hind}(T) = R \\left( \\ln q_\\mathrm{hind}(T) + \\frac{\\sum_i E_i e^{-\\beta E_i}}{RT \\sum_i e^{-\\beta E_i}} \\right) to obtain the entropy. """ if self.fourier is not None: cython.declare(S=cython.double, E=numpy.ndarray, e_kT=numpy.ndarray, i=cython.int) E = self.energies S = constants.R * numpy.log(self.getPartitionFunction(T)) e_kT = numpy.exp(-E / constants.R / T) S += numpy.sum(E*e_kT) / (T * numpy.sum(e_kT)) return S else: Tlow = T * 0.999 Thigh = T * 1.001 return (numpy.log(self.getPartitionFunction(Thigh)) + T * (numpy.log(self.getPartitionFunction(Thigh)) - numpy.log(self.getPartitionFunction(Tlow))) / (Thigh - Tlow)) * constants.R
def convolve(rho1, rho2, Elist): """ Convolutes two density of states arrays `rho1` and `rho2` with corresponding energies `Elist` together using the equation .. math:: \\rho(E) = \\int_0^E \\rho_1(x) \\rho_2(E-x) \\, dx The units of the parameters do not matter so long as they are consistent. """ cython.declare(rho=numpy.ndarray, found1=cython.bint, found2=cython.bint) cython.declare(dE=cython.double, nE=cython.int, i=cython.int, j=cython.int) rho = numpy.zeros_like(Elist) found1 = rho1.any(); found2 = rho2.any() if not found1 and not found2: pass elif found1 and not found2: rho = rho1 elif not found1 and found2: rho = rho2 else: dE = Elist[1] - Elist[0] nE = len(Elist) for i in range(nE): for j in range(i+1): rho[i] += rho2[i-j] * rho1[i] * dE return rho
def getPartitionFunction(self, T): """ Return the value of the partition function at the specified temperature `T` in K. The formula is .. math:: q_\\mathrm{rot}(T) = \\frac{8 \\pi^2 I k_\\mathrm{B} T}{\\sigma h^2} for linear rotors and .. math:: q_\\mathrm{rot}(T) = \\frac{\\sqrt{\\pi}}{\\sigma} \\left( \\frac{8 \\pi^2 k_\\mathrm{B} T}{h^2} \\right)^{3/2} \\sqrt{I_\\mathrm{A} I_\\mathrm{B} I_\\mathrm{C}} for nonlinear rotors. Above, :math:`T` is temperature, :math:`\\sigma` is the symmetry number, :math:`I` is the moment of inertia, :math:`k_\\mathrm{B}` is the Boltzmann constant, and :math:`h` is the Planck constant. """ cython.declare(theta=cython.double, inertia=cython.double) if self.linear: inertia = self.inertia.value theta = constants.h * constants.h / (8 * constants.pi * constants.pi * inertia * constants.kB) return T / theta / self.symmetry else: theta = 1.0 for inertia in self.inertia.values: theta *= constants.h * constants.h / (8 * constants.pi * constants.pi * inertia * constants.kB) return numpy.sqrt(constants.pi * T**len(self.inertia.values) / theta) / self.symmetry
def __exploreCyclesRecursively(self, chain, cycleList): """ Finds cycles by spidering through a graph. Give it a chain of atoms that are connected, `chain`, and a list of cycles found so far `cycleList`. If `chain` is a cycle, it is appended to `cycleList`. Then chain is expanded by one atom (in each available direction) and the function is called again. This recursively spiders outwards from the starting chain, finding all the cycles. """ vertex2 = cython.declare(Vertex) edge = cython.declare(Edge) # chainLabels = cython.declare(list) # chainLabels=[self.keys().index(v) for v in chain] # print "found %d so far. Chain=%s"%(len(cycleList),chainLabels) for vertex2, edge in self.edges[chain[-1]].iteritems(): # vertex2 will loop through each of the atoms # that are bonded to the last atom in the chain. if vertex2 is chain[0] and len(chain) > 2: # it is the first atom in the chain - so the chain IS a cycle! cycleList.append(chain[:]) elif vertex2 not in chain: # make the chain a little longer and explore again chain.append(vertex2) cycleList = self.__exploreCyclesRecursively(chain, cycleList) # any cycles down this path (-vertex2) have now been found, # so remove the vertex from the chain chain.pop(-1) return cycleList
def _pickle_context(ctx): """ returns a picklable tuple of args to be passed to _unpickle_context The context and all of its shifted contexts are pickled, along with and node states that exist for any of those contexts. """ shifted_ctx = cython.declare(MDFContext) node = cython.declare(MDFNode) node_state = cython.declare(NodeState) all_ctx_ids = [ctx.get_id()] # get the shift sets for all shifted contexts shift_sets = [] for shifted_ctx in ctx.get_shifted_contexts(): shift_set = shifted_ctx.get_shift_set() shift_sets.append((shifted_ctx.get_id(), shift_set)) all_ctx_ids.append(shifted_ctx.get_id()) # get the cached values for all nodes in any of the contexts we're interested in node_states = [] for node in _all_nodes.itervalues(): for ctx_id, node_state in node._states.iteritems(): if ctx_id in all_ctx_ids: node_states.append((ctx_id, node, NodeStateWrapper(node_state))) return (ctx.__class__, ctx.get_id(), ctx.get_date(), node_states, shift_sets)
def isSpecificCaseOf(self, other): """ Returns ``True`` if `other` is the same as `self` or is a more specific case of `self`. Returns ``False`` if some of `self` is not included in `other` or they are mutually exclusive. """ cython.declare(gb=GroupBond) if not isinstance(other, GroupBond): # Let the isSpecificCaseOf method of other handle it # We expect self to be a Bond object, but can't test for it here # because that would create an import cycle return other.isSpecificCaseOf(self) gb = other cython.declare(order1=str, order2=str) # Compare two bond groups for equivalence # Each atom type in self must have an equivalent in other for order1 in self.order: # all these must match for order2 in gb.order: # can match any of these if order1 == order2: break else: return False # Otherwise self is in fact a specific case of other return True
def isSubgraphIsomorphic(self, other, initialMap=None): """ Returns ``True`` if `other` is subgraph isomorphic and ``False`` otherwise. In other words, return ``True`` if self is more specific than other. The `initialMap` attribute can be used to specify a required mapping from `self` to `other` (i.e. the atoms of `self` are the keys, while the atoms of `other` are the values). The `other` parameter must be a :class:`Group` object, or a :class:`TypeError` is raised. """ cython.declare(group=Group) cython.declare(mult1=cython.short, mult2=cython.short) # It only makes sense to compare a Group to a Group for subgraph # isomorphism, so raise an exception if this is not what was requested if not isinstance(other, Group): raise TypeError( 'Got a {0} object for parameter "other", when a Group object is required.'.format(other.__class__) ) group = other if self.multiplicity: for mult1 in self.multiplicity: if group.multiplicity: for mult2 in group.multiplicity: if mult1 == mult2: break else: return False else: if group.multiplicity: return False # Do the isomorphism comparison return Graph.isSubgraphIsomorphic(self, other, initialMap)
def generateResonanceIsomers(mol): """ Generate and return all of the resonance isomers of this molecule. """ cython.declare(isomers=list, newIsomers=list, index=cython.int, atom=Atom) cython.declare(isomer=Molecule, newIsomer=Molecule, isom=Molecule) isomers = [mol] # Iterate over resonance isomers index = 0 while index < len(isomers): isomer = isomers[index] newIsomers = [] for algo in populate_resonance_generation_algorithm(): newIsomers.extend(algo(isomer)) for newIsomer in newIsomers: # Append to isomer list if unique for isom in isomers: if isom.isIsomorphic(newIsomer): break else: isomers.append(newIsomer) # Move to next resonance isomer index += 1 return isomers
def equivalent(self, other): """ Returns ``True`` if `other` is equivalent to `self` or ``False`` if not, where `other` can be either an :class:`Bond` or an :class:`GroupBond` object. """ cython.declare(gb=GroupBond) if not isinstance(other, GroupBond): # Let the equivalent method of other handle it # We expect self to be a Bond object, but can't test for it here # because that would create an import cycle return other.equivalent(self) gb = other cython.declare(order1=str, order2=str) # Compare two bond groups for equivalence # Each atom type in self must have an equivalent in other (and vice versa) for order1 in self.order: for order2 in gb.order: if order1 == order2: break else: return False for order1 in gb.order: for order2 in self.order: if order1 == order2: break else: return False # Otherwise the two bond groups are equivalent return True
def _shift(self, shift_set_, cache_context=True): """ see shift - this is the implementation called from C, split out so other C functions can call this directly """ # if there's nothing to shift return this context if not shift_set_: return self # check the type of shift_set_ and construct a new ShiftSet if required shift_set = cython.declare(ShiftSet) if not isinstance(shift_set_, ShiftSet): shift_set = ShiftSet(shift_set_) else: shift_set = shift_set_ # if a context already exists for this shift set then return it. parent = cython.declare(MDFContext) parent = self._parent or self try: shift_key = shift_set._get_shift_key(self) return parent._shifted_cache[shift_key] except KeyError: pass # return a new shifted context return MDFContext(self._now, _shift_parent=self, _shift_set=shift_set, _cache_shifted=cache_context)
def isBalanced(self): """ Return ``True`` if the reaction has the same number of each atom on each side of the reaction equation, or ``False`` if not. """ from rmgpy.molecule.element import elementList cython.declare(reactantElements=dict, productElements=dict, molecule=Molecule, atom=Atom, element=Element) reactantElements = {}; productElements = {} for element in elementList: reactantElements[element] = 0 productElements[element] = 0 for reactant in self.reactants: if isinstance(reactant, Species): molecule = reactant.molecule[0] elif isinstance(reactant, Molecule): molecule = reactant for atom in molecule.atoms: reactantElements[atom.element] += 1 for product in self.products: if isinstance(product, Species): molecule = product.molecule[0] elif isinstance(product, Molecule): molecule = product for atom in molecule.atoms: productElements[atom.element] += 1 for element in elementList: if reactantElements[element] != productElements[element]: return False return True
def fixBarrierHeight(self, forcePositive=False): """ Turns the kinetics into Arrhenius (if they were ArrheniusEP) and ensures the activation energy is at least the endothermicity for endothermic reactions, and is not negative only as a result of using Evans Polanyi with an exothermic reaction. If `forcePositive` is True, then all reactions are forced to have a non-negative barrier. """ cython.declare(H0=cython.double, H298=cython.double, Ea=cython.double) H298 = self.getEnthalpyOfReaction(298) H0 = sum([spec.thermo.E0.value_si for spec in self.products]) - sum([spec.thermo.E0.value_si for spec in self.reactants]) if isinstance(self.kinetics, ArrheniusEP): Ea = self.kinetics.E0.value_si # temporarily using Ea to store the intrinsic barrier height E0 self.kinetics = self.kinetics.toArrhenius(H298) if Ea > 0 and self.kinetics.Ea.value_si < 0: self.kinetics.comment += "\nEa raised from {0:.1f} to 0 kJ/mol.".format(self.kinetics.Ea.value_si/1000) logging.info("For reaction {1!s} Ea raised from {0:.1f} to 0 kJ/mol.".format(self.kinetics.Ea.value_si/1000, self)) self.kinetics.Ea.value_si = 0 if isinstance(self.kinetics, Arrhenius): Ea = self.kinetics.Ea.value_si if H0 > 0 and Ea < H0: self.kinetics.Ea.value_si = H0 self.kinetics.comment += "\nEa raised from {0:.1f} to {1:.1f} kJ/mol to match endothermicity of reaction.".format(Ea/1000,H0/1000) logging.info("For reaction {2!s}, Ea raised from {0:.1f} to {1:.1f} kJ/mol to match endothermicity of reaction.".format(Ea/1000, H0/1000, self)) if forcePositive and isinstance(self.kinetics, Arrhenius) and self.kinetics.Ea.value_si < 0: self.kinetics.comment += "\nEa raised from {0:.1f} to 0 kJ/mol.".format(self.kinetics.Ea.value_si/1000) logging.info("For reaction {1!s} Ea raised from {0:.1f} to 0 kJ/mol.".format(self.kinetics.Ea.value_si/1000, self)) self.kinetics.Ea.value_si = 0
def integrate(exp,downer,upper): if(upper=='inf'): upper=100000 if(exp.isnumeric()): return(int(exp)*(upper-downer)) dx=(upper-downer)/1000 val1=cython.declare(cython.float) val2=cython.declare(cython.float) arr=[0] if(upper==downer): sum=0.0 elif(upper>downer): j=downer sum=0.0 while(j<upper): arr[0]=j val1=extract.main(exp,arr) arr[0]=j+dx val2=extract.main(exp,arr) sum+=(val1+val2)/2*dx j=j+dx else: j=upper sum=0.0 while(j<downer): arr[0]=j val1=extract.main(exp,arr) arr[0]=j-dx val2=extract.main(exp,arr) sum+=(val1+val2)/2*dx j=j-dx return sum
def getFeatures(atom, bonds): """ Returns a list of features needed to determine atomType for :class:'Atom' or :class:'GroupAtom' object 'atom and with local bond structure `bonds`, a ``dict`` containing atom-bond pairs. """ cython.declare(single=cython.int, allDouble=cython.int, rDouble=cython.int, sDouble=cython.int, oDouble=cython.int, triple=cython.int, benzene=cython.int) cython.declare(features=cython.list) # Count numbers of each higher-order bond type single = 0; rDouble = 0; oDouble = 0; sDouble = 0; triple = 0; benzene = 0 for atom2, bond12 in bonds.iteritems(): if bond12.isSingle(): single += 1 elif bond12.isDouble(): if atom2.isOxygen(): oDouble += 1 elif atom2.isSulfur(): sDouble += 1 else: # rDouble is for double bonds NOT to oxygen or Sulfur rDouble += 1 elif bond12.isTriple(): triple += 1 elif bond12.isBenzene(): benzene += 1 # allDouble is for all double bonds, to anything allDouble = rDouble + oDouble + sDouble features = [single, allDouble, rDouble, oDouble, sDouble, triple, benzene, atom.lonePairs] return features
def getEquilibriumConstant(self, T, type='Kc'): """ Return the equilibrium constant for the reaction at the specified temperature `T` in K. The `type` parameter lets you specify the quantities used in the equilibrium constant: ``Ka`` for activities, ``Kc`` for concentrations (default), or ``Kp`` for pressures. Note that this function currently assumes an ideal gas mixture. """ cython.declare(dGrxn=cython.double, K=cython.double, C0=cython.double, P0=cython.double) # Use free energy of reaction to calculate Ka dGrxn = self.getFreeEnergyOfReaction(T) K = numpy.exp(-dGrxn / constants.R / T) # Convert Ka to Kc or Kp if specified P0 = 1e5 if type == 'Kc': # Convert from Ka to Kc; C0 is the reference concentration C0 = P0 / constants.R / T K *= C0 ** (len(self.products) - len(self.reactants)) elif type == 'Kp': # Convert from Ka to Kp; P0 is the reference pressure K *= P0 ** (len(self.products) - len(self.reactants)) elif type != 'Ka' and type != '': raise ReactionError('Invalid type "%s" passed to Reaction.getEquilibriumConstant(); should be "Ka", "Kc", or "Kp".') if K == 0: raise ReactionError('Got equilibrium constant of 0') return K
def __cpp_calculate_phi(self, x, t): cython.declare(xx=cython.double[3]) xx[0] = x[0] xx[1] = x[1] xx[2] = x[2] level = self.WT.mwl + self.WT.eta(xx,t) return x[self.vert_axis]-level
def find_lone_pair_multiple_bond_paths(atom1): """ Find all the delocalization paths between lone electron pair and multiple bond in a 3-atom system `atom1` indicates the localized lone pair site. Currently carbenes are excluded from this path. Examples: - N2O (N#[N+][O-] <-> [N-]=[N+]=O) - Azide (N#[N+][NH-] <-> [N-]=[N+]=N <-> [N-2][N+]#[NH+]) - N#N group on sulfur (O[S-](O)[N+]#N <-> OS(O)=[N+]=[N-] <-> O[S+](O)#[N+][N-2]) - N[N+]([O-])=O <=> N[N+](=O)[O-], these structures are isomorphic but not identical, this transition is important for correct degeneracy calculations """ cython.declare(paths=list, atom2=Atom, atom3=Atom, bond12=Bond, bond23=Bond) # No paths if atom1 has no lone pairs, or cannot lose them, or is a carbon atom if atom1.lonePairs <= 0 or not is_atom_able_to_lose_lone_pair(atom1) or atom1.isCarbon(): return [] paths = [] for atom2, bond12 in atom1.edges.items(): # Bond must be capable of gaining an order if bond12.isSingle() or bond12.isDouble(): for atom3, bond23 in atom2.edges.items(): # Bond must be capable of losing an order without breaking, atom3 must be able to gain a lone pair if atom1 is not atom3 and (bond23.isDouble() or bond23.isTriple())\ and (atom3.isCarbon() or is_atom_able_to_gain_lone_pair(atom3)): paths.append([atom1, atom2, atom3, bond12, bond23]) return paths
def find_N5dc_radical_delocalization_paths(atom1): """ Find all the resonance structures of an N5dc nitrogen atom with a single bond to a radical N/O/S site, another single bond to a negatively charged N/O/S site, and one double bond (not participating in this transformation) Example: - N=[N+]([O])([O-]) <=> N=[N+]([O-])([O]), these structures are isomorphic but not identical, the transition is important for correct degeneracy calculations In this transition atom1 is the middle N+ (N5dc), atom2 is the radical site, and atom3 is negatively charged A "if atom1.atomType.label == 'N5dc'" check should be done before calling this function """ cython.declare(paths=list, atom2=Atom, atom3=Atom, bond12=Bond, bond23=Bond) path = [] for atom2, bond12 in atom1.edges.items(): if atom2.radicalElectrons and bond12.isSingle() and not atom2.charge and is_atom_able_to_gain_lone_pair(atom2): for atom3, bond13 in atom1.edges.items(): if (atom2 is not atom3 and bond13.isSingle() and atom3.charge < 0 and is_atom_able_to_lose_lone_pair(atom3)): path.append([atom2, atom3]) return path # there could only be one such path per atom1, return if found return path
def getAtomType(atom, bonds): """ Determine the appropriate atom type for an :class:`Atom` object `atom` with local bond structure `bonds`, a ``dict`` containing atom-bond pairs. """ cython.declare(atomSymbol=str) cython.declare(molFeatureList=cython.list, atomTypeFeatureList=cython.list) # Use element and counts to determine proper atom type atomSymbol = atom.symbol #These elements do not do not have a more specific atomType if atomSymbol in nonSpecifics: return atomTypes[atomSymbol] molFeatureList = getFeatures(atom, bonds) for specificAtomType in atomTypes[atomSymbol].specific: atomtypeFeatureList = specificAtomType.getFeatures() for molFeature, atomtypeFeature in zip(molFeatureList, atomtypeFeatureList): if atomtypeFeature == []: continue elif molFeature not in atomtypeFeature: break else: return specificAtomType else: rDouble = molFeatureList[2] oDouble = molFeatureList[3] sDouble = molFeatureList[4] triple = molFeatureList[5] benzene = molFeatureList[6] raise AtomTypeError('Unable to determine atom type for atom {0}, which has {1:d} double bonds to C, {2:d} double bonds to O, {3:d} double bonds to S, {4:d} triple bonds, and {5:d} benzene bonds.'.format(atom, rDouble, oDouble, sDouble, triple, benzene))
def findAllDelocalizationPathsLonePairRadical(atom1): """ Find all the delocalization paths of lone electron pairs next to the radical center indicated by `atom1`. Used to generate resonance isomers. """ cython.declare(paths=list) cython.declare(atom2=Atom, bond12=Bond) # No paths if atom1 is not a radical if atom1.radicalElectrons <= 0: return [] # In a first step we only consider nitrogen and oxygen atoms as possible radical centers if not ((atom1.lonePairs == 0 and atom1.isNitrogen()) or(atom1.lonePairs == 2 and atom1.isOxygen())): return [] # Find all delocalization paths paths = [] for atom2, bond12 in atom1.edges.items(): # Only single bonds are considered if bond12.isSingle(): # Neighboring atom must posses a lone electron pair to loose it if ((atom2.lonePairs == 1 and atom2.isNitrogen()) or (atom2.lonePairs == 3 and atom2.isOxygen())) and (atom2.radicalElectrons == 0): paths.append([atom1, atom2]) return paths
def getElement(value): """ Return the :class:`Element` object corresponding to the given parameter `value`. If an integer is provided, the value is treated as the atomic number. If a string is provided, the value is treated as the symbol. An :class:`ElementError` is raised if no matching element is found. """ cython.declare(element=Element, number=cython.int, symbol=str) if isinstance(value, int) or isinstance(value, long): # The parameter is an integer; assume this is the atomic number number = value for element in elementList: if element.number == number: return element # If we reach this point that means we did not find an appropriate element, # so we raise an exception raise ElementError("No element found with atomic number %i." % (number)) elif isinstance(value, str): # The parameter is a string; assume this is the element symbol symbol = value for element in elementList: if element.symbol == symbol: return element # If we reach this point that means we did not find an appropriate element, # so we raise an exception raise ElementError("No element found with symbol %s." % (symbol)) else: raise ElementError('No element found based on parameter %s "%r".' % (type(value), value))
def isSpecificCaseOf(self, other): """ Return ``True`` if `self` is a specific case of `other`, or ``False`` otherwise. If `other` is an :class:`Atom` object, then this is the same as the :meth:`equivalent()` method. If `other` is an :class:`GroupAtom` object, then the atom must match or be more specific than any of the combinations in the atom pattern. """ if isinstance(other, Atom): return self.equivalent(other) elif isinstance(other, GroupAtom): cython.declare(atom=GroupAtom, a=AtomType, radical=cython.short, spin=cython.short, charge=cython.short) atom = other for a in atom.atomType: if self.atomType.isSpecificCaseOf(a): break else: return False for radical, spin in zip(atom.radicalElectrons, atom.spinMultiplicity): if self.radicalElectrons == radical and self.spinMultiplicity == spin: break else: return False for charge in atom.charge: if self.charge == charge: break else: return False return True
def equivalent(self, other): """ Return ``True`` if `other` is indistinguishable from this atom, or ``False`` otherwise. If `other` is an :class:`Atom` object, then all attributes except `label` must match exactly. If `other` is an :class:`GroupAtom` object, then the atom must match any of the combinations in the atom pattern. """ cython.declare(atom=Atom, ap=GroupAtom) if isinstance(other, Atom): atom = other return (self.element is atom.element and self.radicalElectrons == atom.radicalElectrons and self.spinMultiplicity == atom.spinMultiplicity and self.charge == atom.charge) elif isinstance(other, GroupAtom): cython.declare(a=AtomType, radical=cython.short, spin=cython.short, charge=cython.short) ap = other for a in ap.atomType: if self.atomType.equivalent(a): break else: return False for radical, spin in zip(ap.radicalElectrons, ap.spinMultiplicity): if self.radicalElectrons == radical and self.spinMultiplicity == spin: break else: return False for charge in ap.charge: if self.charge == charge: break else: return False return True
def toOBMol(self): """ Convert a molecular structure to an OpenBabel OBMol object. Uses `OpenBabel <http://openbabel.org/>`_ to perform the conversion. """ cython.declare(atom=Atom, atom1=Atom, bonds=dict, atom2=Atom, bond=Bond) cython.declare(index1=cython.int, index2=cython.int, order=cython.int) # Sort the atoms before converting to ensure output is consistent # between different runs self.sortAtoms() atoms = self.vertices obmol = openbabel.OBMol() for atom in atoms: a = obmol.NewAtom() a.SetAtomicNum(atom.number) a.SetFormalCharge(atom.charge) orders = {'S': 1, 'D': 2, 'T': 3, 'B': 5} for atom1 in self.vertices: for atom2, bond in atom1.edges.iteritems(): index1 = atoms.index(atom1) index2 = atoms.index(atom2) if index1 < index2: order = orders[bond.order] obmol.AddBond(index1+1, index2+1, order) obmol.AssignSpinMultiplicity(True) return obmol
# cython: infer_types=True, language_level=3, py2_import=True # # Cython Scanner # import sys import os import platform import cython cython.declare(EncodedString=object, string_prefixes=object, raw_prefixes=object, IDENT=unicode, print_function=object) from Cython import Plex, Utils from Cython.Plex.Scanners import Scanner from Cython.Plex.Errors import UnrecognizedInput from Errors import CompileError, error from Lexicon import string_prefixes, raw_prefixes, make_lexicon, IDENT from Future import print_function from StringEncoding import EncodedString debug_scanner = 0 trace_scanner = 0 scanner_debug_flags = 0 scanner_dump_file = None lexicon = None
def has_reactive_molecule(self): """ `True` if the species has at least one reactive molecule, `False` otherwise """ cython.declare(molecule=Molecule) return any([molecule.reactive for molecule in self.molecule])
def get_resonance_hybrid(self): """ Returns a molecule object with bond orders that are the average of all the resonance structures. """ # get labeled resonance isomers self.generate_resonance_structures(keep_isomorphic=True) # only consider reactive molecules as representative structures molecules = [mol for mol in self.molecule if mol.reactive] # return if no resonance if len(molecules) == 1: return molecules[0] # create a sorted list of atom objects for each resonance structure cython.declare( atomsFromStructures=list, oldAtoms=list, newAtoms=list, numResonanceStructures=cython.short, structureNum=cython.short, oldBondOrder=cython.float, index1=cython.short, index2=cython.short, newMol=Molecule, oldMol=Molecule, atom1=Atom, atom2=Atom, bond=Bond, atoms=list, ) atoms_from_structures = [] for new_mol in molecules: new_mol.atoms.sort(key=lambda atom: atom.id) atoms_from_structures.append(new_mol.atoms) num_resonance_structures = len(molecules) # make original structure with no bonds new_mol = Molecule() original_atoms = atoms_from_structures[0] for atom1 in original_atoms: atom = new_mol.add_atom(Atom(atom1.element)) atom.id = atom1.id new_atoms = new_mol.atoms # initialize bonds to zero order for index1, atom1 in enumerate(original_atoms): for atom2 in atom1.bonds: index2 = original_atoms.index(atom2) bond = Bond(new_atoms[index1], new_atoms[index2], 0) new_mol.add_bond(bond) # set bonds to the proper value for structureNum, oldMol in enumerate(molecules): old_atoms = atoms_from_structures[structureNum] for index1, atom1 in enumerate(old_atoms): # make bond orders average of resonance structures for atom2 in atom1.bonds: index2 = old_atoms.index(atom2) new_bond = new_mol.get_bond(new_atoms[index1], new_atoms[index2]) old_bond_order = oldMol.get_bond( old_atoms[index1], old_atoms[index2]).get_order_num() new_bond.apply_action( ('CHANGE_BOND', None, old_bond_order / num_resonance_structures / 2)) # set radicals in resonance hybrid to maximum of all structures if atom1.radical_electrons > 0: new_atoms[index1].radical_electrons = max( atom1.radical_electrons, new_atoms[index1].radical_electrons) new_mol.update_atomtypes(log_species=False, raise_exception=False) return new_mol
class StrategyBase(Node): """ Strategy Node. Used to define strategy logic within a tree. A Strategy's role is to allocate capital to it's children based on a function. Args: * name (str): Strategy name * children (dict, list): A collection of children. If dict, the format is {name: child}, if list then list of children. Children can be any type of Node. * parent (Node): The parent Node Attributes: * name (str): Strategy name * parent (Strategy): Strategy parent * root (Strategy): Root node of the tree (topmost node) * children (dict): Strategy's children * now (datetime): Used when backtesting to store current date * stale (bool): Flag used to determine if Strategy is stale and need updating * prices (TimeSeries): Prices of the Strategy - basically an index that reflects the value of the strategy over time. * outlays (DataFrame): Outlays for each SecurityBase child * price (float): last price * value (float): last value * weight (float): weight in parent * full_name (str): Name including parents' names * members (list): Current Strategy + strategy's children * securities (list): List of strategy children that are of type SecurityBase * commission_fn (fn(quantity, price)): A function used to determine the commission (transaction fee) amount. Could be used to model slippage (implementation shortfall). Note that often fees are symmetric for buy and sell and absolute value of quantity should be used for calculation. * capital (float): Capital amount in Strategy - cash * universe (DataFrame): Data universe available at the current time. Universe contains the data passed in when creating a Backtest. Use this data to determine strategy logic. """ _capital = cy.declare(cy.double) _net_flows = cy.declare(cy.double) _last_value = cy.declare(cy.double) _last_price = cy.declare(cy.double) _last_fee = cy.declare(cy.double) _paper_trade = cy.declare(cy.bint) bankrupt = cy.declare(cy.bint) def __init__(self, name, children=None, parent=None): Node.__init__(self, name, children=children, parent=parent) self._capital = 0 self._weight = 1 self._value = 0 self._price = 100 # helper vars self._net_flows = 0 self._last_value = 0 self._last_price = 100 self._last_fee = 0 # default commission function self.commission_fn = self._dflt_comm_fn self._paper_trade = False self._positions = None self.bankrupt = False @property def price(self): """ Current price. """ if self.root.stale: self.root.update(self.now, None) return self._price @property def prices(self): """ TimeSeries of prices. """ if self.root.stale: self.root.update(self.now, None) return self._prices.loc[:self.now] @property def values(self): """ TimeSeries of values. """ if self.root.stale: self.root.update(self.now, None) return self._values.loc[:self.now] @property def capital(self): """ Current capital - amount of unallocated capital left in strategy. """ # no stale check needed return self._capital @property def cash(self): """ TimeSeries of unallocated capital. """ # no stale check needed return self._cash @property def fees(self): """ TimeSeries of fees. """ # no stale check needed return self._fees @property def universe(self): """ Data universe available at the current time. Universe contains the data passed in when creating a Backtest. Use this data to determine strategy logic. """ # avoid windowing every time # if calling and on same date return # cached value if self.now == self._last_chk: return self._funiverse else: self._last_chk = self.now self._funiverse = self._universe.loc[:self.now] return self._funiverse @property def securities(self): """ Returns a list of children that are of type SecurityBase """ return [x for x in self.members if isinstance(x, SecurityBase)] @property def outlays(self): """ Returns a DataFrame of outlays for each child SecurityBase """ return pd.DataFrame({x.name: x.outlays for x in self.securities}) @property def positions(self): """ TimeSeries of positions. """ # if accessing and stale - update first if self.root.stale: self.root.update(self.root.now, None) vals = pd.DataFrame({ x.name: x.positions for x in self.members if isinstance(x, SecurityBase) }) self._positions = vals return vals def setup(self, universe): """ Setup strategy with universe. This will speed up future calculations and updates. """ # save full universe in case we need it self._original_data = universe # determine if needs paper trading # and setup if so if self is not self.parent: self._paper_trade = True self._paper_amount = 1000000 paper = deepcopy(self) paper.parent = paper paper.root = paper paper._paper_trade = False paper.setup(self._original_data) paper.adjust(self._paper_amount) self._paper = paper # setup universe funiverse = universe if self._universe_tickers is not None: # if we have universe_tickers defined, limit universe to # those tickers valid_filter = list( set(universe.columns).intersection(self._universe_tickers)) funiverse = universe[valid_filter].copy() # if we have strat children, we will need to create their columns # in the new universe if self._has_strat_children: for c in self._strat_children: funiverse[c] = np.nan # must create to avoid pandas warning funiverse = pd.DataFrame(funiverse) self._universe = funiverse # holds filtered universe self._funiverse = funiverse self._last_chk = None # We're not bankrupt yet self.bankrupt = False # setup internal data self.data = pd.DataFrame(index=funiverse.index, columns=['price', 'value', 'cash', 'fees'], data=0.0) self._prices = self.data['price'] self._values = self.data['value'] self._cash = self.data['cash'] self._fees = self.data['fees'] # setup children as well - use original universe here - don't want to # pollute with potential strategy children in funiverse if self.children is not None: [c.setup(universe) for c in self._childrenv] @cy.locals(newpt=cy.bint, val=cy.double, ret=cy.double) def update(self, date, data=None, inow=None): """ Update strategy. Updates prices, values, weight, etc. """ # resolve stale state self.root.stale = False # update helpers on date change # also set newpt flag newpt = False if self.now == 0: newpt = True elif date != self.now: self._net_flows = 0 self._last_price = self._price self._last_value = self._value self._last_fee = 0.0 newpt = True # update now self.now = date if inow is None: if self.now == 0: inow = 0 else: inow = self.data.index.get_loc(date) # update children if any and calculate value val = self._capital # default if no children if self.children is not None: for c in self._childrenv: # avoid useless update call if c._issec and not c._needupdate: continue c.update(date, data, inow) val += c.value if self.root == self: if (val < 0) and not self.bankrupt: # Declare a bankruptcy self.bankrupt = True self.flatten() # update data if this value is different or # if now has changed - avoid all this if not since it # won't change if newpt or self._value != val: self._value = val self._values.values[inow] = val bottom = self._last_value + self._net_flows if bottom != 0: ret = self._value / (self._last_value + self._net_flows) - 1 else: if self._value == 0: ret = 0 else: raise ZeroDivisionError( 'Could not update %s. Last value ' 'was %s and net flows were %s. Current' 'value is %s. Therefore, ' 'we are dividing by zero to obtain the return ' 'for the period.' % (self.name, self._last_value, self._net_flows, self._value)) self._price = self._last_price * (1 + ret) self._prices.values[inow] = self._price # update children weights if self.children is not None: for c in self._childrenv: # avoid useless update call if c._issec and not c._needupdate: continue if val != 0: c._weight = c.value / val else: c._weight = 0.0 # if we have strategy children, we will need to update them in universe if self._has_strat_children: for c in self._strat_children: # TODO: optimize ".loc" here as well self._universe.loc[date, c] = self.children[c].price # Cash should track the unallocated capital at the end of the day, so # we should update it every time we call "update". # Same for fees self._cash.values[inow] = self._capital self._fees.values[inow] = self._last_fee # update paper trade if necessary if newpt and self._paper_trade: self._paper.update(date) self._paper.run() self._paper.update(date) # update price self._price = self._paper.price self._prices.values[inow] = self._price @cy.locals(amount=cy.double, update=cy.bint, flow=cy.bint, fees=cy.double) def adjust(self, amount, update=True, flow=True, fee=0.0): """ Adjust capital - used to inject capital to a Strategy. This injection of capital will have no effect on the children. Args: * amount (float): Amount to adjust by. * update (bool): Force update? * flow (bool): Is this adjustment a flow? A flow will not have an impact on the performance (price index). Example of flows are simply capital injections (say a monthly contribution to a portfolio). This should not be reflected in the returns. A non-flow (flow=False) does impact performance. A good example of this is a commission, or a dividend. """ # adjust capital self._capital += amount self._last_fee += fee # if flow - increment net_flows - this will not affect # performance. Commissions and other fees are not flows since # they have a performance impact if flow: self._net_flows += amount if update: # indicates that data is now stale and must # be updated before access self.root.stale = True @cy.locals(amount=cy.double, update=cy.bint) def allocate(self, amount, child=None, update=True): """ Allocate capital to Strategy. By default, capital is allocated recursively down the children, proportionally to the children's weights. If a child is specified, capital will be allocated to that specific child. Allocation also have a side-effect. They will deduct the same amount from the parent's "account" to offset the allocation. If there is remaining capital after allocation, it will remain in Strategy. Args: * amount (float): Amount to allocate. * child (str): If specified, allocation will be directed to child only. Specified by name. * update (bool): Force update. """ # allocate to child if child is not None: if child not in self.children: c = SecurityBase(child) c.setup(self._universe) # update to bring up to speed c.update(self.now) # add child to tree self._add_child(c) # allocate to child self.children[child].allocate(amount) # allocate to self else: # adjust parent's capital # no need to update now - avoids repetition if self.parent == self: self.parent.adjust(-amount, update=False, flow=True) else: # do NOT set as flow - parent will be another strategy # and therefore should not incur flow self.parent.adjust(-amount, update=False, flow=False) # adjust self's capital self.adjust(amount, update=False, flow=True) # push allocation down to children if any # use _weight to avoid triggering an update if self.children is not None: [ c.allocate(amount * c._weight, update=False) for c in self._childrenv ] # mark as stale if update requested if update: self.root.stale = True @cy.locals(delta=cy.double, weight=cy.double, base=cy.double) def rebalance(self, weight, child, base=np.nan, update=True): """ Rebalance a child to a given weight. This is a helper method to simplify code logic. This method is used when we want to se the weight of a particular child to a set amount. It is similar to allocate, but it calculates the appropriate allocation based on the current weight. Args: * weight (float): The target weight. Usually between -1.0 and 1.0. * child (str): child to allocate to - specified by name. * base (float): If specified, this is the base amount all weight delta calculations will be based off of. This is useful when we determine a set of weights and want to rebalance each child given these new weights. However, as we iterate through each child and call this method, the base (which is by default the current value) will change. Therefore, we can set this base to the original value before the iteration to ensure the proper allocations are made. * update (bool): Force update? """ # if weight is 0 - we want to close child if weight == 0: if child in self.children: return self.close(child) else: return # if no base specified use self's value if np.isnan(base): base = self.value # else make sure we have child if child not in self.children: c = SecurityBase(child) c.setup(self._universe) # update child to bring up to speed c.update(self.now) self._add_child(c) # allocate to child # figure out weight delta c = self.children[child] delta = weight - c.weight c.allocate(delta * base) def close(self, child): """ Close a child position - alias for rebalance(0, child). This will also flatten (close out all) the child's children. Args: * child (str): Child, specified by name. """ c = self.children[child] # flatten if children not None if c.children is not None and len(c.children) != 0: c.flatten() if c.value != 0. and not np.isnan(c.value): c.allocate(-c.value) def flatten(self): """ Close all child positions. """ # go right to base alloc [c.allocate(-c.value) for c in self._childrenv if c.value != 0] def run(self): """ This is the main logic method. Override this method to provide some algorithm to execute on each date change. This method is called by backtester. """ pass def set_commissions(self, fn): """ Set commission (transaction fee) function. Args: fn (fn(quantity, price)): Function used to determine commission amount. """ self.commission_fn = fn for c in self._childrenv: if isinstance(c, StrategyBase): c.set_commissions(fn) @cy.locals(q=cy.double, p=cy.double) def _dflt_comm_fn(self, q, p): return 0.
#======================================================================= # # Python Lexical Analyser # # # Scanning an input stream # #======================================================================= import cython cython.declare(BOL=object, EOL=object, EOF=object, NOT_FOUND=object) from . import Errors from .Regexps import BOL, EOL, EOF NOT_FOUND = object() class Scanner(object): """ A Scanner is used to read tokens from a stream of characters using the token set specified by a Plex.Lexicon. Constructor: Scanner(lexicon, stream, name = '') See the docstring of the __init__ method for details. Methods: See the docstrings of the individual methods for more
class SecurityBase(Node): """ Security Node. Used to define a security within a tree. A Security's has no children. It simply models an asset that can be bought or sold. Args: * name (str): Security name * multiplier (float): security multiplier - typically used for derivatives. Attributes: * name (str): Security name * parent (Security): Security parent * root (Security): Root node of the tree (topmost node) * now (datetime): Used when backtesting to store current date * stale (bool): Flag used to determine if Security is stale and need updating * prices (TimeSeries): Security prices. * price (float): last price * outlays (TimeSeries): Series of outlays. Positive outlays mean capital was allocated to security and security consumed that amount. Negative outlays are the opposite. This can be useful for calculating turnover at the strategy level. * value (float): last value - basically position * price * multiplier * weight (float): weight in parent * full_name (str): Name including parents' names * members (list): Current Security + strategy's children * position (float): Current position (quantity). """ _last_pos = cy.declare(cy.double) _position = cy.declare(cy.double) multiplier = cy.declare(cy.double) _prices_set = cy.declare(cy.bint) _needupdate = cy.declare(cy.bint) _outlay = cy.declare(cy.double) @cy.locals(multiplier=cy.double) def __init__(self, name, multiplier=1): Node.__init__(self, name, parent=None, children=None) self._value = 0 self._price = 0 self._weight = 0 self._position = 0 self.multiplier = multiplier # opt self._last_pos = 0 self._issec = True self._needupdate = True self._outlay = 0 @property def price(self): """ Current price. """ # if accessing and stale - update first if self._needupdate or self.now != self.parent.now: self.update(self.root.now) return self._price @property def prices(self): """ TimeSeries of prices. """ # if accessing and stale - update first if self._needupdate or self.now != self.parent.now: self.update(self.root.now) return self._prices.loc[:self.now] @property def values(self): """ TimeSeries of values. """ # if accessing and stale - update first if self._needupdate or self.now != self.parent.now: self.update(self.root.now) if self.root.stale: self.root.update(self.root.now, None) return self._values.loc[:self.now] @property def position(self): """ Current position """ # no stale check needed return self._position @property def positions(self): """ TimeSeries of positions. """ # if accessing and stale - update first if self._needupdate: self.update(self.root.now) if self.root.stale: self.root.update(self.root.now, None) return self._positions.loc[:self.now] @property def outlays(self): """ TimeSeries of outlays. Positive outlays (buys) mean this security received and consumed capital (capital was allocated to it). Negative outlays are the opposite (the security close/sold, and returned capital to parent). """ # if accessing and stale - update first return self._outlays.loc[:self.now] def setup(self, universe): """ Setup Security with universe. Speeds up future runs. Args: * universe (DataFrame): DataFrame of prices with security's name as one of the columns. """ # if we already have all the prices, we will store them to speed up # future updates try: prices = universe[self.name] except KeyError: prices = None # setup internal data if prices is not None: self._prices = prices self.data = pd.DataFrame(index=universe.index, columns=['value', 'position'], data=0.0) self._prices_set = True else: self.data = pd.DataFrame(index=universe.index, columns=['price', 'value', 'position']) self._prices = self.data['price'] self._prices_set = False self._values = self.data['value'] self._positions = self.data['position'] # add _outlay self.data['outlay'] = 0. self._outlays = self.data['outlay'] @cy.locals(prc=cy.double) def update(self, date, data=None, inow=None): """ Update security with a given date and optionally, some data. This will update price, value, weight, etc. """ # filter for internal calls when position has not changed - nothing to # do. Internal calls (stale root calls) have None data. Also want to # make sure date has not changed, because then we do indeed want to # update. if date == self.now and self._last_pos == self._position: return if inow is None: if date == 0: inow = 0 else: inow = self.data.index.get_loc(date) # date change - update price if date != self.now: # update now self.now = date if self._prices_set: self._price = self._prices.values[inow] # traditional data update elif data is not None: prc = data[self.name] self._price = prc self._prices.values[inow] = prc self._positions.values[inow] = self._position self._last_pos = self._position if np.isnan(self._price): if self._position == 0: self._value = 0 else: raise Exception( 'Position is open (non-zero) and latest price is NaN ' 'for security %s. Cannot update node value.' % self.name) else: self._value = self._position * self._price * self.multiplier self._values.values[inow] = self._value if self._weight == 0 and self._position == 0: self._needupdate = False # save outlay to outlays if self._outlay != 0: self._outlays.values[inow] = self._outlay # reset outlay back to 0 self._outlay = 0 @cy.locals(amount=cy.double, update=cy.bint, q=cy.double, outlay=cy.double, i=cy.int) def allocate(self, amount, update=True): """ This allocates capital to the Security. This is the method used to buy/sell the security. A given amount of shares will be determined on the current price, a commission will be calculated based on the parent's commission fn, and any remaining capital will be passed back up to parent as an adjustment. Args: * amount (float): Amount of adjustment. * update (bool): Force update? """ # will need to update if this has been idle for a while... # update if needupdate or if now is stale # fetch parent's now since our now is stale if self._needupdate or self.now != self.parent.now: self.update(self.parent.now) # ignore 0 alloc # Note that if the price of security has dropped to zero, then it # should never be selected by SelectAll, SelectN etc. I.e. we should # not open the position at zero price. At the same time, we are able # to close it at zero price, because at that point amount=0. # Note also that we don't erase the position in an asset which price # has dropped to zero (though the weight will indeed be = 0) if amount == 0: return if self.parent is self or self.parent is None: raise Exception('Cannot allocate capital to a parentless security') if self._price == 0 or np.isnan(self._price): raise Exception('Cannot allocate capital to ' '%s because price is %s as of %s' % (self.name, self._price, self.parent.now)) # buy/sell # determine quantity - must also factor in commission # closing out? if amount == -self._value: q = -self._position else: q = amount / (self._price * self.multiplier) if self.integer_positions: if (self._position > 0) or ((self._position == 0) and (amount > 0)): # if we're going long or changing long position q = math.floor(q) else: # if we're going short or changing short position q = math.ceil(q) # if q is 0 nothing to do if q == 0 or np.isnan(q): return # unless we are closing out a position (q == -position) # we want to ensure that # # - In the event of a positive amount, this indicates the maximum # amount a given security can use up for a purchase. Therefore, if # commissions push us above this amount, we cannot buy `q`, and must # decrease its value # # - In the event of a negative amount, we want to 'raise' at least the # amount indicated, no less. Therefore, if we have commission, we must # sell additional units to fund this requirement. As such, q must once # again decrease. # if not q == -self._position: full_outlay, _, _ = self.outlay(q) # if full outlay > amount, we must decrease the magnitude of `q` # this can potentially lead to an infinite loop if the commission # per share > price per share. However, we cannot really detect # that in advance since the function can be non-linear (say a fn # like max(1, abs(q) * 0.01). Nevertheless, we want to avoid these # situations. # cap the maximum number of iterations to 1e4 and raise exception # if we get there # if integer positions then we know we are stuck if q doesn't change # if integer positions is false then we want full_outlay == amount # if integer positions is true then we want to be at the q where # if we bought 1 more then we wouldn't have enough cash i = 0 last_q = q last_amount_short = full_outlay - amount while not np.isclose(full_outlay, amount, rtol=0.) and q != 0: dq_wout_considering_tx_costs = (full_outlay - amount) / ( self._price * self.multiplier) q = q - dq_wout_considering_tx_costs if self.integer_positions: q = math.floor(q) full_outlay, _, _ = self.outlay(q) # if our q is too low and we have integer positions # then we know that the correct quantity is the one where # the outlay of q + 1 < amount. i.e. if we bought one more # position then we wouldn't have enough cash if self.integer_positions: full_outlay_of_1_more, _, _ = self.outlay(q + 1) if full_outlay < amount and full_outlay_of_1_more > amount: break # if not integer positions then we should keep going until # full_outlay == amount or is close enough i = i + 1 if i > 1e4: raise Exception( 'Potentially infinite loop detected. This occurred ' 'while trying to reduce the amount of shares purchased' ' to respect the outlay <= amount rule. This is most ' 'likely due to a commission function that outputs a ' 'commission that is greater than the amount of cash ' 'a short sale can raise.') if self.integer_positions and last_q == q: raise Exception( 'Newton Method like root search for quantity is stuck!' ' q did not change in iterations so it is probably a bug' ' but we are not entirely sure it is wrong! Consider ' ' changing to warning.') last_q = q if np.abs(full_outlay - amount) > np.abs(last_amount_short): raise Exception( 'The difference between what we have raised with q and' ' the amount we are trying to raise has gotten bigger since' ' last iteration! full_outlay should always be approaching' ' amount! There may be a case where the commission fn is' ' not smooth') last_amount_short = full_outlay - amount # if last step led to q == 0, then we can return just like above if q == 0: return # this security will need an update, even if pos is 0 (for example if # we close the positions, value and pos is 0, but still need to do that # last update) self._needupdate = True # adjust position & value self._position += q # calculate proper adjustment for parent # parent passed down amount so we want to pass # -outlay back up to parent to adjust for capital # used full_outlay, outlay, fee = self.outlay(q) # store outlay for future reference self._outlay += outlay # call parent self.parent.adjust(-full_outlay, update=update, flow=False, fee=fee) @cy.locals(q=cy.double, p=cy.double) def commission(self, q, p): """ Calculates the commission (transaction fee) based on quantity and price. Uses the parent's commission_fn. Args: * q (float): quantity * p (float): price """ return self.parent.commission_fn(q, p) @cy.locals(q=cy.double) def outlay(self, q): """ Determines the complete cash outlay (including commission) necessary given a quantity q. Second returning parameter is a commission itself. Args: * q (float): quantity """ fee = self.commission(q, self._price * self.multiplier) outlay = q * self._price * self.multiplier return outlay + fee, outlay, fee def run(self): """ Does nothing - securities have nothing to do on run. """ pass
def fromRDKitMol(mol, rdkitmol): """ Convert a RDKit Mol object `rdkitmol` to a molecular structure. Uses `RDKit <http://rdkit.org/>`_ to perform the conversion. This Kekulizes everything, removing all aromatic atom types. """ cython.declare(i=cython.int, radicalElectrons=cython.int, charge=cython.int, lonePairs=cython.int, number=cython.int, order=cython.float, atom=mm.Atom, atom1=mm.Atom, atom2=mm.Atom, bond=mm.Bond) mol.vertices = [] # Add hydrogen atoms to complete molecule if needed rdkitmol.UpdatePropertyCache(strict=False) rdkitmol = Chem.AddHs(rdkitmol) Chem.rdmolops.Kekulize(rdkitmol, clearAromaticFlags=True) # iterate through atoms in rdkitmol for i in xrange(rdkitmol.GetNumAtoms()): rdkitatom = rdkitmol.GetAtomWithIdx(i) # Use atomic number as key for element number = rdkitatom.GetAtomicNum() isotope = rdkitatom.GetIsotope() element = elements.getElement(number, isotope or -1) # Process charge charge = rdkitatom.GetFormalCharge() radicalElectrons = rdkitatom.GetNumRadicalElectrons() atom = mm.Atom(element, radicalElectrons, charge, '', 0) mol.vertices.append(atom) # Add bonds by iterating again through atoms for j in xrange(0, i): rdkitbond = rdkitmol.GetBondBetweenAtoms(i, j) if rdkitbond is not None: order = 0 # Process bond type rdbondtype = rdkitbond.GetBondType() if rdbondtype.name == 'SINGLE': order = 1 elif rdbondtype.name == 'DOUBLE': order = 2 elif rdbondtype.name == 'TRIPLE': order = 3 elif rdbondtype.name == 'AROMATIC': order = 1.5 bond = mm.Bond(mol.vertices[i], mol.vertices[j], order) mol.addBond(bond) # We need to update lone pairs first because the charge was set by RDKit mol.updateLonePairs() # Set atom types and connectivity values mol.update() # Assume this is always true # There are cases where 2 radicalElectrons is a singlet, but # the triplet is often more stable, mol.multiplicity = mol.getRadicalCount() + 1 # mol.updateAtomTypes() return mol
def equivalent(self, other): """ Returns ``True`` if `other` is equivalent to `self` or ``False`` if not, where `other` can be either an :class:`Atom` or an :class:`GroupAtom` object. When comparing two :class:`GroupAtom` objects, this function respects wildcards, e.g. ``R!H`` is equivalent to ``C``. """ cython.declare(group=GroupAtom) if not isinstance(other, GroupAtom): # Let the equivalent method of other handle it # We expect self to be an Atom object, but can't test for it here # because that would create an import cycle return other.equivalent(self) group = other cython.declare(atomType1=AtomType, atomtype2=AtomType, radical1=cython.short, radical2=cython.short, lp1=cython.short, lp2=cython.short, charge1=cython.short, charge2=cython.short) # Compare two atom groups for equivalence # Each atom type in self must have an equivalent in other (and vice versa) for atomType1 in self.atomType: for atomType2 in group.atomType: if atomType1.equivalent(atomType2): break else: return False for atomType1 in group.atomType: for atomType2 in self.atomType: if atomType1.equivalent(atomType2): break else: return False # Each free radical electron state in self must have an equivalent in other (and vice versa) for radical1 in self.radicalElectrons: if group.radicalElectrons: # Only check if the list is non-empty. An empty list indicates a wildcard. for radical2 in group.radicalElectrons: if radical1 == radical2: break else: return False for radical1 in group.radicalElectrons: if self.radicalElectrons: for radical2 in self.radicalElectrons: if radical1 == radical2: break else: return False for lp1 in self.lonePairs: if group.lonePairs: for lp2 in group.lonePairs: if lp1 == lp2: break else: return False #Each charge in self must have an equivalent in other (and vice versa) for charge1 in self.charge: if group.charge: for charge2 in group.charge: if charge1 == charge2: break else: return False for charge1 in group.charge: if self.charge: for charge2 in self.charge: if charge1 == charge2: break else: return False # Otherwise the two atom groups are equivalent return True
# cython: infer_types=True, language_level=3, auto_pickle=False # # Cython Scanner # from __future__ import absolute_import import cython cython.declare(make_lexicon=object, lexicon=object, print_function=object, error=object, warning=object, os=object, platform=object) import os import platform from unicodedata import normalize from .. import Utils from ..Plex.Scanners import Scanner from ..Plex.Errors import UnrecognizedInput from .Errors import error, warning from .Lexicon import any_string_prefix, make_lexicon, IDENT from .Future import print_function debug_scanner = 0 trace_scanner = 0 scanner_debug_flags = 0 scanner_dump_file = None lexicon = None
class Node(object): """ The Node is the main building block in bt's tree structure design. Both StrategyBase and SecurityBase inherit Node. It contains the core functionality of a tree node. Args: * name (str): The Node name * parent (Node): The parent Node * children (dict, list): A collection of children. If dict, the format is {name: child}, if list then list of children. Attributes: * name (str): Node name * parent (Node): Node parent * root (Node): Root node of the tree (topmost node) * children (dict): Node's children * now (datetime): Used when backtesting to store current date * stale (bool): Flag used to determine if Node is stale and need updating * prices (TimeSeries): Prices of the Node. Prices for a security will be the security's price, for a strategy it will be an index that reflects the value of the strategy over time. * price (float): last price * value (float): last value * weight (float): weight in parent * full_name (str): Name including parents' names * members (list): Current Node + node's children """ _price = cy.declare(cy.double) _value = cy.declare(cy.double) _weight = cy.declare(cy.double) _issec = cy.declare(cy.bint) _has_strat_children = cy.declare(cy.bint) def __init__(self, name, parent=None, children=None): self.name = name # strategy children helpers self._has_strat_children = False self._strat_children = [] # if children is not None, we assume that we want to limit the # available children space to the provided list. if children is not None: if isinstance(children, list): # if all strings - just save as universe_filter if all(isinstance(x, str) for x in children): self._universe_tickers = children # empty dict - don't want to uselessly create # tons of children when they might not be needed children = {} else: # this will be case if we pass in children # (say a bunch of sub-strategies) tmp = {} ut = [] for c in children: if type(c) == str: tmp[c] = SecurityBase(c) ut.append(c) else: # deepcopy object for possible later reuse tmp[c.name] = deepcopy(c) # if strategy, turn on flag and add name to list # strategy children have special treatment if isinstance(c, StrategyBase): self._has_strat_children = True self._strat_children.append(c.name) # if not strategy, then we will want to add this to # universe_tickers to filter on setup else: ut.append(c.name) children = tmp # we want to keep whole universe in this case # so set to None self._universe_tickers = ut if parent is None: self.parent = self self.root = self # by default all positions are integer self.integer_positions = True else: self.parent = parent self.root = parent.root parent._add_child(self) # default children if children is None: children = {} self._universe_tickers = None self.children = children self._childrenv = list(children.values()) for c in self._childrenv: c.parent = self c.root = self.root # set default value for now self.now = 0 # make sure root has stale flag # used to avoid unnecessary update # sometimes we change values in the tree and we know that we will need # to update if another node tries to access a given value (say weight). # This avoid calling the update until it is actually needed. self.root.stale = False # helper vars self._price = 0 self._value = 0 self._weight = 0 # is security flag - used to avoid updating 0 pos securities self._issec = False def __getitem__(self, key): return self.children[key] def use_integer_positions(self, integer_positions): """ Set indicator to use (or not) integer positions for a given strategy or security. By default all positions in number of stocks should be integer. However this may lead to unexpected results when working with adjusted prices of stocks. Because of series of reverse splits of stocks, the adjusted prices back in time might be high. Thus rounding of desired amount of stocks to buy may lead to having 0, and thus ignoring this stock from backtesting. """ self.integer_positions = integer_positions for c in self._childrenv: c.use_integer_positions(integer_positions) @property def prices(self): """ A TimeSeries of the Node's price. """ # can optimize depending on type - # securities don't need to check stale to # return latest prices, whereas strategies do... raise NotImplementedError() @property def price(self): """ Current price of the Node """ # can optimize depending on type - # securities don't need to check stale to # return latest prices, whereas strategies do... raise NotImplementedError() @property def value(self): """ Current value of the Node """ if self.root.stale: self.root.update(self.root.now, None) return self._value @property def weight(self): """ Current weight of the Node (with respect to the parent). """ if self.root.stale: self.root.update(self.root.now, None) return self._weight def setup(self, dates): """ Setup method used to initialize a Node with a set of dates. """ raise NotImplementedError() def _add_child(self, child): child.parent = self child.root = self.root child.integer_positions = self.integer_positions if self.children is None: self.children = {child.name: child} else: self.children[child.name] = child self._childrenv = list(self.children.values()) def update(self, date, data=None, inow=None): """ Update Node with latest date, and optionally some data. """ raise NotImplementedError() def adjust(self, amount, update=True, isflow=True): """ Adjust Node value by amount. """ raise NotImplementedError() def allocate(self, amount, update=True): """ Allocate capital to Node. """ raise NotImplementedError() @property def members(self): """ Node members. Members include current node as well as Node's children. """ res = [self] for c in list(self.children.values()): res.extend(c.members) return res @property def full_name(self): if self.parent == self: return self.name else: return '%s>%s' % (self.parent.full_name, self.name) def __repr__(self): return '<%s %s>' % (self.__class__.__name__, self.full_name)
# cython: language_level=3str # cython: auto_pickle=True from __future__ import absolute_import import cython cython.declare(PyrexTypes=object, ExprNodes=object, Nodes=object, Builtin=object, InternalError=object, error=object, warning=object, fake_rhs_expr=object, TypedExprNode=object) from . import Builtin from . import ExprNodes from . import Nodes from . import Options from . import PyrexTypes from .Visitor import TreeVisitor, CythonTransform from .Errors import error, warning, InternalError from .Optimize import ConstantFolding class TypedExprNode(ExprNodes.ExprNode): # Used for declaring assignments of a specified type without a known entry. def __init__(self, type, may_be_none=None, pos=None): super(TypedExprNode, self).__init__(pos)
# modulation # layer mapping # precoding # mapping to resource elements # OFDM signal generation # # Input to the physical layer: codewords # # # Copyright (c) 2015 - 2016 Austin Aigbe ([email protected]) import cython from libc.math cimport * # Frame structure (4) Ts = cython.declare(cython.double, 1/(15000*2048)) # seconds (basic time unit) Tf = cython.declare(cython.double, 307200 * Ts) # 10ms duration (radio frame duration) LTE_PHY_RADIO_FRAME_TYPE1 = "FDD" LTE_PHY_RADIO_FRAME_TYPE2 = "TDD" LTE_PHY_FDD_HALF_DUPLEX = 0 LTE_PHY_FDD_FULL_DUPLEX = 1 # Frame structure type 1 (4.1) # # 1 radio frame (Tf) = 20 slots = 10 subframes = 10ms # 1 slot (T_slot_fdd) = 0.5ms # 1 subframe = 2 slots = 1ms [2i and 2i+1, i = subframe] # # For FDD, DL transmission = 10 subframes, UL transmission = 10 subframes
def generate_aryne_resonance_structures(mol): """ Generate aryne resonance structures, including the cumulene and alkyne forms. For all 6-membered rings, check for the following bond patterns: - DDDSDS - STSDSD This does NOT cover all possible aryne resonance forms, only the simplest ones. Especially for polycyclic arynes, enumeration of all resonance forms is related to enumeration of all Kekule structures, which is very difficult. """ cython.declare(rings=list, ring=list, new_mol_list=list, bond_list=list, i=cython.int, j=cython.int, bond_orders=str, new_orders=str, ind=cython.int, bond=Bond, new_mol=Molecule) rings = mol.get_relevant_cycles() rings = [ring for ring in rings if len(ring) == 6] new_mol_list = [] for ring in rings: # Get bond orders bond_list = mol.get_edges_in_cycle(ring) bond_orders = ''.join([bond.get_order_str() for bond in bond_list]) new_orders = None # Check for expected bond patterns if bond_orders.count('T') == 1: # Reorder the list so the triple bond is first ind = bond_orders.index('T') bond_orders = bond_orders[ind:] + bond_orders[:ind] bond_list = bond_list[ind:] + bond_list[:ind] # Check for patterns if bond_orders == 'TSDSDS': new_orders = 'DDSDSD' elif bond_orders.count('D') == 4: # Search for DDD and reorder the list so that it comes first if 'DDD' in bond_orders: ind = bond_orders.index('DDD') bond_orders = bond_orders[ind:] + bond_orders[:ind] bond_list = bond_list[ind:] + bond_list[:ind] elif bond_orders.startswith('DD') and bond_orders.endswith('D'): bond_orders = bond_orders[-1:] + bond_orders[:-1] bond_list = bond_list[-1:] + bond_list[:-1] elif bond_orders.startswith('D') and bond_orders.endswith('DD'): bond_orders = bond_orders[-2:] + bond_orders[:-2] bond_list = bond_list[-2:] + bond_list[:-2] # Check for patterns if bond_orders == 'DDDSDS': new_orders = 'STSDSD' if new_orders is not None: # We matched one of our patterns, so we can now change the bonds for i, bond in enumerate(bond_list): bond.set_order_str(new_orders[i]) # Make a copy of the molecule new_mol = mol.copy(deep=True) # Undo the changes to the current molecule for i, bond in enumerate(bond_list): bond.set_order_str(bond_orders[i]) # Try to update atom types try: new_mol.update_atomtypes(log_species=False) except AtomTypeError: pass # Don't append resonance structure if it creates an undefined atomtype else: new_mol_list.append(new_mol) return new_mol_list
def calcContactC(numNodes, nAtoms, cutoffDist, tmpDists, tmpDistsAtms, contactMat, atomToNode, nodeGroupIndicesNP, nodeGroupIndicesNPAux): '''Translates MDAnalysis distance calculation to node contact matrix. This function is Cython compiled to optimize the search for nodes in contact. It relies on the results of MDAnalysis' `self_distance_array` calculation, stored in a 1D NumPy array of shape (n*(n-1)/2,), which acts as an unwrapped triangular matrix. In this function, the distances between all atoms in an atom groups of all pairs of nodes are verified to check if any pair of atoms were closer than a cutoff distance. This is done for all pairs of nodes in the system, and all frames in the trajectory. The pre-allocated contact matrix passed as an argument to this function is used to store the number of frames where each pair of nodes had at least one contact. Args: numNodes (int): Number of nodes in the system. nAtoms (int) : Number of atoms in atom groups represented by system nodes. Usually hydrogen atoms are not included in contact detection, and are not present in atom groups. cutoffDist (float) : Distance at which atoms are no longer considered 'in contact'. tmpDists (obj) : Temporary pre-allocated NumPy array with atom distances. This is the result of MDAnalysis `self_distance_array` calculation. tmpDistsAtms (obj) : Temporary pre-allocated NumPy array to store the shortest distance between atoms in different nodes. contactMat (obj) : Pre-allocated NumPy matrix where node contacts will be stored. atomToNode (obj) : NumPy array that maps atoms in atom groups to their respective nodes. nodeGroupIndicesNP (obj) : NumPy array with atom indices for all atoms in each node group. nodeGroupIndicesNPAux (obj) : Auxiliary NumPy array with the indices of the first atom in each atom group, as listed in `nodeGroupIndicesNP`. ''' #cdef int nextIfirst, j1, jend, i, nodeI_k, nodeAtmIndx, nodeAtmIndxNext nextIfirst = cython.declare(cython.int) # Cython types are evaluated as for cdef declarations j1: cython.int jend: cython.int i: cython.int nodeI_k: cython.int nodeAtmIndx: cython.int nodeAtmIndxNext: cython.int # We iterate untill we have only one node left for i in range(numNodes - 1): # Initializes the array for this node. tmpDistsAtms.fill(cutoffDist*2) # index of first atom in node "i" nodeAtmIndx = nodeGroupIndicesNPAux[i] # index of first atom in node "i+1" nodeAtmIndxNext = nodeGroupIndicesNPAux[i+1] # Gets first atom in next node nextIfirst = nodeGroupIndicesNP[nodeAtmIndxNext] # Per node: Iterate over atoms from node i for nodeI_k in nodeGroupIndicesNP[nodeAtmIndx:nodeAtmIndxNext]: # Go from 2D indices to 1D (n*(n-1)/2) indices: j1 = getLinIndexC(nodeI_k, nextIfirst, nAtoms) jend = j1 + (nAtoms - nextIfirst) # Gets the shortest distance between atoms in different nodes tmpDistsAtms[nextIfirst:] = np.where(tmpDists[j1: jend] < tmpDistsAtms[nextIfirst:], tmpDists[j1: jend], tmpDistsAtms[nextIfirst:]) # Adds one to the contact to indicate that this frame had a contact. contactMat[i, np.unique( atomToNode[ np.where(tmpDistsAtms < cutoffDist)[0] ] )] += 1
def _generate_resonance_structures(mol_list, method_list, keep_isomorphic=False, copy=False, filter_structures=True): """ Iteratively generate all resonance structures for a list of starting molecules using the specified methods. Args: mol_list starting list of molecules method_list list of resonance structure algorithms keep_isomorphic if False, removes any structures that give is_isomorphic=True (default) if True, only remove structures that give is_identical=True copy if False, append new resonance structures to input list (default) if True, make a new list with all of the resonance structures """ cython.declare(index=cython.int, molecule=Molecule, new_mol_list=list, new_mol=Molecule, mol=Molecule) if copy: # Make a copy of the list so we don't modify the input list mol_list = mol_list[:] min_octet_deviation = min(filtration.get_octet_deviation_list(mol_list)) min_charge_span = min(filtration.get_charge_span_list(mol_list)) # Iterate over resonance structures index = 0 while index < len(mol_list): molecule = mol_list[index] new_mol_list = [] # On-the-fly filtration: Extend methods only for molecule that don't deviate too much from the octet rule # (a +2 distance from the minimal deviation is used, octet deviations per species are in increments of 2) # Sometimes rearranging the structure requires an additional higher charge span structure, so allow # structures with a +1 higher charge span compared to the minimum, e.g., [O-]S#S[N+]#N # This is run by default even if filter_structures=False. octet_deviation = filtration.get_octet_deviation(molecule) charge_span = molecule.get_charge_span() if octet_deviation <= min_octet_deviation + 2 and charge_span <= min_charge_span + 1: for method in method_list: new_mol_list.extend(method(molecule)) if octet_deviation < min_octet_deviation: # update min_octet_deviation to make this criterion tighter min_octet_deviation = octet_deviation if charge_span < min_charge_span: # update min_charge_span to make this criterion tighter min_charge_span = charge_span for new_mol in new_mol_list: # Append to structure list if unique for mol in mol_list: if not keep_isomorphic and mol.is_isomorphic(new_mol): break elif keep_isomorphic and mol.is_identical(new_mol): break else: mol_list.append(new_mol) # Move to the next resonance structure index += 1 # check net charge for mol in mol_list: if mol.get_net_charge() != 0: raise ResonanceError('Resonance generation gave a net charged molecule:\n{0}' 'Ions are not yet supported in RMG.'.format( mol.to_adjacency_list())) return mol_list
def _clar_optimization(mol, constraints=None, max_num=None): """ Implements linear programming algorithm for finding Clar structures. This algorithm maximizes the number of Clar sextets within the constraints of molecular geometry and atom valency. Returns a list of valid Clar solutions in the form of a tuple, with the following entries: [0] Molecule object [1] List of aromatic rings [2] List of bonds [3] Optimization solution The optimization solution is a list of boolean values with sextet assignments followed by double bond assignments, with indices corresponding to the list of aromatic rings and list of bonds, respectively. Method adapted from: Hansen, P.; Zheng, M. The Clar Number of a Benzenoid Hydrocarbon and Linear Programming. J. Math. Chem. 1994, 15 (1), 93–107. """ cython.declare(molecule=Molecule, aromatic_rings=list, exo=list, l=cython.int, m=cython.int, n=cython.int, a=list, objective=list, status=cython.int, solution=list, innerSolutions=list) from lpsolve55 import lpsolve # Make a copy of the molecule so we don't destroy the original molecule = mol.copy(deep=True) aromatic_rings = molecule.get_aromatic_rings()[0] aromatic_rings.sort(key=lambda x: sum([atom.id for atom in x])) if not aromatic_rings: return [] # Get list of atoms that are in rings atoms = set() for ring in aromatic_rings: atoms.update(ring) atoms = sorted(atoms, key=lambda x: x.id) # Get list of bonds involving the ring atoms, ignoring bonds to hydrogen bonds = set() for atom in atoms: bonds.update([atom.bonds[key] for key in atom.bonds.keys() if key.is_non_hydrogen()]) bonds = sorted(bonds, key=lambda x: (x.atom1.id, x.atom2.id)) # Identify exocyclic bonds, and save their bond orders exo = [] for bond in bonds: if bond.atom1 not in atoms or bond.atom2 not in atoms: if bond.is_double(): exo.append(1) else: exo.append(0) else: exo.append(None) # Dimensions l = len(aromatic_rings) m = len(atoms) n = l + len(bonds) # Connectivity matrix which indicates which rings and bonds each atom is in # Part of equality constraint Ax=b a = [] for atom in atoms: in_ring = [1 if atom in ring else 0 for ring in aromatic_rings] in_bond = [1 if atom in [bond.atom1, bond.atom2] else 0 for bond in bonds] a.append(in_ring + in_bond) # Objective vector for optimization: sextets have a weight of 1, double bonds have a weight of 0 objective = [1] * l + [0] * len(bonds) # Solve LP problem using lpsolve lp = lpsolve('make_lp', m, n) # initialize lp with constraint matrix with m rows and n columns lpsolve('set_verbose', lp, 2) # reduce messages from lpsolve lpsolve('set_obj_fn', lp, objective) # set objective function lpsolve('set_maxim', lp) # set solver to maximize objective lpsolve('set_mat', lp, a) # set left hand side to constraint matrix lpsolve('set_rh_vec', lp, [1] * m) # set right hand side to 1 for all constraints for i in range(m): # set all constraints as equality constraints lpsolve('set_constr_type', lp, i + 1, '=') lpsolve('set_binary', lp, [True] * n) # set all variables to be binary # Constrain values of exocyclic bonds, since we don't want to modify them for i in range(l, n): if exo[i - l] is not None: # NOTE: lpsolve indexes from 1, so the variable we're changing should be i + 1 lpsolve('set_bounds', lp, i + 1, exo[i - l], exo[i - l]) # Add constraints to problem if provided if constraints is not None: for constraint in constraints: try: lpsolve('add_constraint', lp, constraint[0], '<=', constraint[1]) except Exception as e: logging.debug('Unable to add constraint: {0} <= {1}'.format(constraint[0], constraint[1])) logging.debug(mol.to_adjacency_list()) if str(e) == 'invalid vector.': raise ILPSolutionError('Unable to add constraint, likely due to ' 'inconsistent aromatic ring perception.') else: raise status = lpsolve('solve', lp) obj_val, solution = lpsolve('get_solution', lp)[0:2] lpsolve('delete_lp', lp) # Delete the LP problem to clear up memory # Check that optimization was successful if status != 0: raise ILPSolutionError('Optimization could not find a valid solution.') # Check that we the result contains at least one aromatic sextet if obj_val == 0: return [] # Check that the solution contains the maximum number of sextets possible if max_num is None: max_num = obj_val # This is the first solution, so the result should be an upper limit elif obj_val < max_num: raise ILPSolutionError('Optimization obtained a sub-optimal solution.') if any([x != 1 and x != 0 for x in solution]): raise ILPSolutionError('Optimization obtained a non-integer solution.') # Generate constraints based on the solution obtained y = solution[0:l] new_a = y + [0] * len(bonds) new_b = sum(y) - 1 if constraints is not None: constraints.append((new_a, new_b)) else: constraints = [(new_a, new_b)] # Run optimization with additional constraints try: inner_solutions = _clar_optimization(mol, constraints=constraints, max_num=max_num) except ILPSolutionError: inner_solutions = [] return inner_solutions + [(molecule, aromatic_rings, bonds, solution)]
class SecurityBase(Node): """ Security Node. Used to define a security within a tree. A Security's has no children. It simply models an asset that can be bought or sold. Args: * name (str): Security name * multiplier (float): security multiplier - typically used for derivatives. Attributes: * name (str): Security name * parent (Security): Security parent * root (Security): Root node of the tree (topmost node) * now (datetime): Used when backtesting to store current date * stale (bool): Flag used to determine if Security is stale and need updating * prices (TimeSeries): Security prices. * price (float): last price * value (float): last value - basically position * price * multiplier * weight (float): weight in parent * full_name (str): Name including parents' names * members (list): Current Security + strategy's children * position (float): Current position (quantity). """ _last_pos = cy.declare(cy.double) _position = cy.declare(cy.double) multiplier = cy.declare(cy.double) _prices_set = cy.declare(cy.bint) _needupdate = cy.declare(cy.bint) @cy.locals(multiplier=cy.double) def __init__(self, name, multiplier=1): Node.__init__(self, name, parent=None, children=None) self._value = 0 self._price = 0 self._weight = 0 self._position = 0 self.multiplier = multiplier # opt self._last_pos = 0 self._issec = True self._needupdate = True @property def price(self): """ Current price. """ # if accessing and stale - update first if self._needupdate or self.now != self.parent.now: self.update(self.root.now) return self._price @property def prices(self): """ TimeSeries of prices. """ # if accessing and stale - update first if self._needupdate or self.now != self.parent.now: self.update(self.root.now) return self._prices.ix[:self.now] @property def values(self): """ TimeSeries of values. """ # if accessing and stale - update first if self._needupdate or self.now != self.parent.now: self.update(self.root.now) if self.root.stale: self.root.update(self.root.now, None) return self._values.ix[:self.now] @property def position(self): """ Current position """ # no stale check needed return self._position @property def positions(self): """ TimeSeries of positions. """ # if accessing and stale - update first if self._needupdate: self.update(self.root.now) if self.root.stale: self.root.update(self.root.now, None) return self._positions.ix[:self.now] def setup(self, universe): """ Setup Security with universe. Speeds up future runs. Args: * universe (DataFrame): DataFrame of prices with security's name as one of the columns. """ # if we already have all the prices, we will store them to speed up # future udpates try: prices = universe[self.name] except KeyError: prices = None # setup internal data if prices is not None: self._prices = prices self.data = pd.DataFrame(index=universe.index, columns=['value', 'position'], data=0.0) self._prices_set = True else: self.data = pd.DataFrame(index=universe.index, columns=['price', 'value', 'position']) self._prices = self.data['price'] self._prices_set = False self._values = self.data['value'] self._positions = self.data['position'] @cy.locals(prc=cy.double) def update(self, date, data=None, inow=None): """ Update security with a given date and optionally, some data. This will update price, value, weight, etc. """ # filter for internal calls when position has not changed - nothing to # do. Internal calls (stale root calls) have None data. Also want to # make sure date has not changed, because then we do indeed want to # update. if date == self.now and self._last_pos == self._position: return if inow is None: if date == 0: inow = 0 else: inow = self.data.index.get_loc(date) # date change - update price if date != self.now: # update now self.now = date if self._prices_set: self._price = self._prices.values[inow] # traditional data update elif data is not None: prc = data[self.name] self._price = prc self._prices.values[inow] = prc self._positions.values[inow] = self._position self._last_pos = self._position self._value = self._position * self._price * self.multiplier self._values.values[inow] = self._value if self._weight == 0 and self._position == 0: self._needupdate = False @cy.locals(amount=cy.double, update=cy.bint, q=cy.double, outlay=cy.double) def allocate(self, amount, update=True): """ This allocates capital to the Security. This is the method used to buy/sell the security. A given amount of shares will be determined on the current price, a commisison will be calculated based on the parent's commission fn, and any remaining capital will be passed back up to parent as an adjustment. Args: * amount (float): Amount of adjustment. * update (bool): Force update? """ # will need to update if this has been idle for a while... # update if needupdate or if now is stale # fetch parent's now since our now is stale if self._needupdate or self.now != self.parent.now: self.update(self.parent.now) # ignore 0 alloc # Note that if the price of security has dropped to zero, then it should # never be selected by SelectAll, SelectN etc. I.e. we should not open # the position at zero price. At the same time, we are able to close # it at zero price, because at that point amount=0. # Note also that we don't erase the position in an asset which price has # dropped to zero (though the weight will indeed be = 0) if amount == 0: return if self.parent is self or self.parent is None: raise Exception( 'Cannot allocate capital to a parentless security') if self._price == 0 or np.isnan(self._price): raise Exception( 'Cannot allocate capital to ' '%s because price is %s as of %s' % (self.name, self._price, self.parent.now)) # buy/sell # determine quantity - must also factor in commission # closing out? if amount == -self._value: q = -self._position else: q = amount / (self._price * self.multiplier) if self.integer_positions: if (self._position > 0) or ((self._position == 0) and (amount > 0)): # if we're going long or changing long position q = math.floor(q) else: # if we're going short or changing short position q = math.ceil(q) # if q is 0 nothing to do if q == 0 or np.isnan(q): return # this security will need an update, even if pos is 0 (for example if # we close the positions, value and pos is 0, but still need to do that # last update) self._needupdate = True # adjust position & value self._position += q # calculate proper adjustment for parent # parent passed down amount so we want to pass # -outlay back up to parent to adjust for capital # used outlay, fee = self.outlay(q) # call parent self.parent.adjust(-outlay, update=update, flow=False, fee=fee) @cy.locals(q=cy.double, p=cy.double) def commission(self, q, p): """ Calculates the commission (transaction fee) based on quantity and price. Uses the parent's commission_fn. Args: * q (float): quantity * p (float): price """ return self.parent.commission_fn(q, p) @cy.locals(q=cy.double) def outlay(self, q): """ Determines the complete cash outlay (including commission) necessary given a quantity q. Second returning parameter is a commission itself. Args: * q (float): quantity """ fee = self.commission(q, self._price * self.multiplier) full_outlay = q * self._price * self.multiplier + fee return full_outlay, fee def run(self): """ Does nothing - securities have nothing to do on run. """ pass
def generate_optimal_aromatic_resonance_structures(mol, features=None): """ Generate the aromatic form of the molecule. For radicals, generates the form with the most aromatic rings. Returns result as a list. In most cases, only one structure will be returned. In certain cases where multiple forms have the same number of aromatic rings, multiple structures will be returned. If there's an error (eg. in RDKit) it just returns an empty list. """ cython.declare(molecule=Molecule, rings=list, aromaticBonds=list, kekuleList=list, maxNum=cython.int, mol_list=list, new_mol_list=list, ring=list, bond=Bond, order=float, originalBonds=list, originalOrder=list, i=cython.int, counter=cython.int) if features is None: features = analyze_molecule(mol) if not features['is_cyclic']: return [] # Copy the molecule so we don't affect the original molecule = mol.copy(deep=True) # Attempt to rearrange electrons to obtain a structure with the most aromatic rings # Possible rearrangements include aryne resonance and allyl resonance res_list = [generate_aryne_resonance_structures] if features['is_radical'] and not features['is_aryl_radical']: res_list.append(generate_allyl_delocalization_resonance_structures) if molecule.is_aromatic(): kekule_list = generate_kekule_structure(molecule) else: kekule_list = [molecule] _generate_resonance_structures(kekule_list, res_list) # Sort all of the generated structures by number of perceived aromatic rings mol_dict = {} for mol0 in kekule_list: aromatic_bonds = mol0.get_aromatic_rings()[1] num_aromatic = len(aromatic_bonds) mol_dict.setdefault(num_aromatic, []).append((mol0, aromatic_bonds)) # List of potential number of aromatic rings, sorted from largest to smallest arom_options = sorted(mol_dict.keys(), reverse=True) new_mol_list = [] for num in arom_options: mol_list = mol_dict[num] # Generate the aromatic resonance structure(s) for mol0, aromatic_bonds in mol_list: # Aromatize the molecule in place result = generate_aromatic_resonance_structure(mol0, aromatic_bonds, copy=False) if not result: # We failed to aromatize this molecule # This could be due to incorrect aromaticity perception by RDKit continue for mol1 in new_mol_list: if mol1.is_isomorphic(mol0): break else: new_mol_list.append(mol0) if new_mol_list: # We found the most aromatic resonance structures so there's no need to try smaller numbers break return new_mol_list
import cython cython.declare(PyrexTypes=object, Naming=object, ExprNodes=object, Nodes=object, Options=object, UtilNodes=object, ModuleNode=object, LetNode=object, LetRefNode=object, TreeFragment=object, TemplateTransform=object, EncodedString=object, error=object, warning=object, copy=object) from . import Builtin from . import ExprNodes from . import Nodes from .PyrexTypes import py_object_type, unspecified_type from .Visitor import TreeVisitor, CythonTransform from .Errors import error, warning, CompileError, InternalError from cython import set class TypedExprNode(ExprNodes.ExprNode): # Used for declaring assignments of a specified type whithout a known entry. def __init__(self, type):
def generate_resonance_structures(mol, clar_structures=True, keep_isomorphic=False, filter_structures=True): """ Generate and return all of the resonance structures for the input molecule. Most of the complexity of this method goes into handling aromatic species, particularly to generate an accurate set of resonance structures that is consistent regardless of the input structure. The following considerations are made: 1. False positives from RDKit aromaticity detection can occur if a molecule has exocyclic double bonds 2. False negatives from RDKit aromaticity detection can occur if a radical is delocalized into an aromatic ring 3. sp2 hybridized radicals in the plane of an aromatic ring do not participate in hyperconjugation 4. Non-aromatic resonance structures of PAHs are not important resonance contributors (assumption) Aromatic species are broken into the following categories for resonance treatment: - Radical polycyclic aromatic species: Kekule structures are generated in order to generate adjacent resonance structures. The resulting structures are then used for Clar structure generation. After all three steps, any non-aromatic structures are removed, under the assumption that they are not important resonance contributors. - Radical monocyclic aromatic species: Kekule structures are generated along with adjacent resonance structures. All are kept regardless of aromaticity because the radical is more likely to delocalize into the ring. - Stable polycyclic aromatic species: Clar structures are generated - Stable monocyclic aromatic species: Kekule structures are generated """ cython.declare(mol_list=list, new_mol_list=list, features=dict, method_list=list) # Check that mol is a valid structure in terms of atomTypes and net charge. Since SMILES with hypervalance # heteroatoms are not always read correctly, print a suggestion to input the structure using an adjList. try: mol.update() except AtomTypeError: logging.error("The following molecule has at least one atom with an undefined atomtype:\n{0}" "\nIf this structure was entered in SMILES, try using the adjacencyList format for an unambiguous" " definition.".format(mol.to_adjacency_list())) raise if mol.get_net_charge() != 0: raise ValueError("Got the following structure:\nSMILES: {0}\nAdjacencyList:\n{1}\nNet charge: {2}\n\n" "Currently RMG cannot process charged species correctly." "\nIf this structure was entered in SMILES, try using the adjacencyList format for an" " unambiguous definition.".format(mol.to_smiles(), mol.to_adjacency_list(), mol.get_net_charge())) if not mol.reactive: raise ResonanceError('Can only generate resonance structures for reactive molecules! Got the following ' 'unreactive structure:\n{0}Reactive = {1}'.format(mol.to_adjacency_list(), mol.reactive)) mol_list = [mol] # Analyze molecule features = analyze_molecule(mol) # Use generate_optimal_aromatic_resonance_structures to check for false positives and negatives if features['is_aromatic'] or (features['is_cyclic'] and features['is_radical'] and not features['is_aryl_radical']): new_mol_list = generate_optimal_aromatic_resonance_structures(mol, features) if len(new_mol_list) == 0: # Encountered false positive, ie. the molecule is not actually aromatic features['is_aromatic'] = False features['isPolycyclicAromatic'] = False else: features['is_aromatic'] = True if len(new_mol_list[0].get_aromatic_rings()[0]) > 1: features['isPolycyclicAromatic'] = True for new_mol in new_mol_list: # Append to structure list if unique if not keep_isomorphic and mol.is_isomorphic(new_mol): continue elif keep_isomorphic and mol.is_identical(new_mol): continue else: mol_list.append(new_mol) # Special handling for aromatic species if features['is_aromatic']: if features['is_radical'] and not features['is_aryl_radical']: _generate_resonance_structures(mol_list, [generate_kekule_structure], keep_isomorphic=keep_isomorphic, filter_structures=filter_structures) _generate_resonance_structures(mol_list, [generate_allyl_delocalization_resonance_structures], keep_isomorphic=keep_isomorphic, filter_structures=filter_structures) if features['isPolycyclicAromatic'] and clar_structures: _generate_resonance_structures(mol_list, [generate_clar_structures], keep_isomorphic=keep_isomorphic, filter_structures=filter_structures) else: _generate_resonance_structures(mol_list, [generate_aromatic_resonance_structure], keep_isomorphic=keep_isomorphic, filter_structures=filter_structures) # Generate remaining resonance structures method_list = populate_resonance_algorithms(features) _generate_resonance_structures(mol_list, method_list, keep_isomorphic=keep_isomorphic, filter_structures=filter_structures) if filter_structures: return filtration.filter_structures(mol_list, features=features) return mol_list
# cython: infer_types=True, language_level=3, py2_import=True # # Cython Scanner # from __future__ import absolute_import import os import platform import cython cython.declare(EncodedString=object, any_string_prefix=unicode, IDENT=unicode, print_function=object, error=object, warning=object) from .. import Utils from ..Plex.Scanners import Scanner from ..Plex.Errors import UnrecognizedInput from .Errors import error, warning from .Lexicon import any_string_prefix, make_lexicon, IDENT from .Future import print_function from .StringEncoding import EncodedString debug_scanner = 0 trace_scanner = 0 scanner_debug_flags = 0 scanner_dump_file = None lexicon = None def get_lexicon():
class GroundTruthRetriever(BuildDirector): prodBuilder = declare(ProductBuilder) def __init__(self): super(GroundTruthRetriever, self).__init__() self.prodBuilder: ProductBuilder = ProductBuilder() self.__loadBuildDirector() def __loadBuildDirector(self) -> None: self.builder = self.prodBuilder @locals(frameArr=ndarray) def __collisionRetrievalOnFrame(self, frameArr: ndarray) -> ndarray: return where(frameArr != 0.) # collect index values bigger than 0. @locals(data=char, trialSize=cint) def groundTruthRetrievalOnTrial(self, dataName: str, direction: str, trialSize: int = 10) -> Dict[str, int]: data: Generator = self.__arrayDataGenerator(dataName, direction=direction, stringArr=False) dataArr: ndarray = None #next(data) counterF: int = 1 counterT: int = 1 currRow: int = None finalArr: Dict[str, int] = dict() #for row in range(self.__dataLength): while True: try: dataArr = next(data) currRow = 1 if dataArr[self.__collisionRetrievalOnFrame( dataArr)].size > 0 else 0 ######### finalArr["frame_" + str(counterF) + ', trial_' + str(counterT)] = currRow counterF += 1 except StopIteration as s: print(s) break if (counterF >= trialSize + 1): counterF = 1 counterT += 1 return finalArr @locals(dataName=char) def __arrayDataGenerator(self, dataName, direction: str, stringArr: bool = True) -> Generator: data: ndarray = self.buildCoordinateData(dataName, direction=direction) self.__dataLength: int = len(data) if (stringArr): return data else: currArr: ndarray = None row: str = None dataArr: List[ndarray] = list() for row in range(len(data)): yield self.__deleteFirstElement( self.__numpyStringToFloat(data[row])) # currArr = self.__deleteFirstElement(self.__numpyStringToFloat(data[row])); #dataArr.append(currArr) # return asarray(dataArr); @staticmethod @locals(oneDimArray=ndarray) def __numpyStringToFloat(oneDimArray: ndarray) -> ndarray: elem: str = None num: str = None floatingArr: ndarray = None try: return oneDimArray.astype(float32) except ValueError as e: try: for elem in range(len(oneDimArray)): floatingArr = asarray( [float(num) for num in oneDimArray[elem].split(",")], dtype=float32) except Exception as e: print(e) return floatingArr @staticmethod @locals(oneDimArray=ndarray) def __deleteFirstElement(oneDimArray: ndarray) -> ndarray: return delete(oneDimArray, 0, None) @staticmethod @locals(dictData=Dict) def sumValuesDict(dictData: Dict) -> str: noncollision: int = sum(value == 0 for value in dictData.values()) collision: int = sum(value == 1 for value in dictData.values()) return "no-collisions: {} | collisions: {}".format( noncollision, collision) @staticmethod @locals(dictData=Dict) def sumValuesDictPerTrial(dictData: Dict[str, int]) -> str: currKey: str = None previousKey: str = None collisions: int = 0 noncollisions: int = 0 tempList: List[int] = list() for key, value in dictData.items(): keys: List[str] = key.split(',') currKey = keys[1] if (currKey == previousKey): tempList.append(value) else: previousKey = currKey if (len(tempList) != 0): #collisions += 1 if 1 in tempList else 0; collisions += tempList.count(1) noncollisions += 1 if not 1 in tempList else 0 tempList.clear() tempList.append(value) return 'number of frames {} | number of trials {} | non-collisions:{} | collisions: {}'.format( len(dictData), len(dictData) // 10, noncollisions, collisions) @staticmethod def cythonFunction(dictData: Dict[str, int]) -> str: return counter.getNonCollisionsOnly(dictData) @staticmethod @locals(dictData=array) def getKeysDictAccordingCollision(dictData: Dict, collision: bool = True) -> List[str]: if (collision): return [key for key, value in dictData.items() if value == 1] elif (collision is False): return [key for key, value in dictData.items() if value == 0]
import cython cython.declare(x=cython.int, y=cython.double) # cdef int x; cdef double y
def find_lowest_u_layer(mol, u_layer, equivalent_atoms): """ Searches for the "minimum" combination of indices of atoms that bear unpaired electrons. It does so by using the information on equivalent atoms to permute equivalent atoms to obtain a combination of atoms that is the (numerically) lowest possible combination. Each possible combination is valid if and only if the distances between the atoms of the combination is identical to the distances between the original combination. First, the algorithm partitions equivalent atoms that bear an unpaired electron. Next, the combinations are generated, and for each combination it is verified whether it pertains to a "valid" combination. Returns a list of indices corresponding to the lowest combination of atom indices bearing unpaired electrons. """ cython.declare( new_u_layer=list, grouped_electrons=list, corresponding_E_layers=list, group=list, e_layer=list, combos=list, orig_agglomerates=list, orig_distances=list, selected_group=list, combo=list, ) if not equivalent_atoms: return u_layer new_u_layer = [] grouped_electrons, corresponding_E_layers = partition( u_layer, equivalent_atoms) # don't process atoms that do not belong to an equivalence layer for group, e_layer in zip(grouped_electrons[:], corresponding_E_layers[:]): if not e_layer: new_u_layer.extend(group) grouped_electrons.remove(group) corresponding_E_layers.remove(e_layer) combos = generate_combo(grouped_electrons, corresponding_E_layers) # compute original distance: orig_agglomerates = agglomerate(grouped_electrons) orig_distances = compute_agglomerate_distance(orig_agglomerates, mol) # deflate the list of lists to be able to numerically compare them selected_group = sorted(itertools.chain.from_iterable(grouped_electrons)) # see if any of the combos is valid and results in a lower numerical combination than the original for combo in combos: if is_valid_combo(combo, mol, orig_distances): combo = sorted(itertools.chain.from_iterable(combo)) if combo < selected_group: selected_group = combo # add the minimized unpaired electron positions to the u-layer: new_u_layer.extend(selected_group) return sorted(new_u_layer)
from __future__ import absolute_import, print_function import sys import inspect from . import TypeSlots from . import Builtin from . import Nodes from . import ExprNodes from . import Errors from . import DebugFlags from . import Future import cython cython.declare(_PRINTABLE=tuple) if sys.version_info[0] >= 3: _PRINTABLE = (bytes, str, int, float) else: _PRINTABLE = (str, unicode, long, int, float) class TreeVisitor(object): """ Base class for writing visitors for a Cython tree, contains utilities for recursing such trees using visitors. Each node is expected to have a child_attrs iterable containing the names of attributes containing child nodes or lists of child nodes. Lists are not considered part of the tree structure (i.e. contained nodes are considered direct children of the parent node).
class SimpleIOPayload(object): """ Represents a payload, i.e. individual response elements, set via SimpleIO. """ sio = cy.declare(cy.object, visibility='public') # type: CySimpleIO all_output_elem_names = cy.declare(list, visibility='public') # type: list output_repeated = cy.declare(cy.bint, visibility='public') # type: bool cid = cy.declare(cy.object, visibility='public') # type: past_unicode data_format = cy.declare(cy.object, visibility='public') # type: past_unicode # One of the two will be used to produce a response user_attrs_dict = cy.declare(dict, visibility='public') # type: dict user_attrs_list = cy.declare(list, visibility='public') # type: list # This is used by Zato internal services only zato_meta = cy.declare(cy.object, visibility='public') # type: object # ################################################################################################################################ def __cinit__(self, sio: CySimpleIO, all_output_elem_names: list, cid, data_format): self.sio = sio self.all_output_elem_names = all_output_elem_names self.output_repeated = self.sio.definition.output_repeated self.cid = cid self.data_format = data_format self.user_attrs_dict = {} self.user_attrs_list = [] self.zato_meta = None # ################################################################################################################################ def __iter__(self): return iter(self.user_attrs_list if self.output_repeated else self. user_attrs_dict) def __setitem__(self, key, value): if isinstance(key, slice): self.user_attrs_list[key.start:key.stop] = value self.output_repeated = True else: setattr(self, key, value) def __setattr__(self, key, value): # Special-case Zato's own internal attributes if key == 'zato_meta': self.zato_meta = value else: self.user_attrs_dict[key] = value def __getattr__(self, key): try: return self.user_attrs_dict[key] except KeyError: raise KeyError('{}; No such key `{}` among `{}` ({})'.format( self.sio.service_class, key, self.user_attrs_dict, hex(id(self)))) # ################################################################################################################################ @cy.returns(bool) def has_data(self): return bool(self.user_attrs_dict or self.user_attrs_list) # ################################################################################################################################ @cy.returns(dict) def _extract_payload_attrs(self, item: object) -> dict: """ Extract response attributes from a single object. Used with items other than dicts. """ extracted: dict = {} is_dict: cy.bint = isinstance(item, dict) # Use a different function depending on whether the object is dict-like or not. # Note that we need .get to be able to provide a default value. has_get = hasattr(item, 'get') # type: bool name = None for name in self.all_output_elem_names: # type: str if is_dict: value = cy.cast(dict, item).get(name, _not_given) elif has_get: value = item.get(name, _not_given) else: value = getattr(item, name, _not_given) if value is not _not_given: extracted[name] = value return extracted # ################################################################################################################################ @cy.returns(dict) def _extract_payload_attrs_dict(self, item: object) -> dict: """ Extract response attributes from a dict. """ extracted: dict = {} name = None for name in self.all_output_elem_names: value = item.get(name, _not_given) if value is not _not_given: extracted[name] = value return extracted # ################################################################################################################################ @cy.cfunc def _is_sqlalchemy(self, item: object): return hasattr(item, '_sa_class_manager') # ################################################################################################################################ @cy.cfunc def _preprocess_payload_attrs(self, value): # First, check if this is not a response from a Zato service wrapped in a response element. # If it is, extract the actual inner response first. if isinstance(value, dict) and len(value) == 1: response_name: str = list(value.keys())[0] # type: str if response_name.startswith('zato') and response_name.endswith( '_response'): return value[response_name] else: return value # ################################################################################################################################ @cy.ccall def set_payload_attrs(self, value: object): """ Assigns user-defined attributes to what will eventually be a response. """ value = self._preprocess_payload_attrs(value) is_dict: cy.bint = isinstance(value, dict) # Shortcut in case we know already this is a dict on input if is_dict: dict_attrs: dict = self._extract_payload_attrs_dict(value) self.user_attrs_dict.update(dict_attrs) else: # Check if this is something that can be explicitly serialised for our purposes if hasattr(value, 'to_zato'): value = value.to_zato() if isinstance(value, (list, tuple)): for item in value: self.user_attrs_list.append( self._extract_payload_attrs(item)) else: self.user_attrs_dict.update(self._extract_payload_attrs(value)) # ################################################################################################################################ @cy.ccall def getvalue(self, serialize: bool = True, force_dict_serialisation: bool = True): """ Returns a service's payload, either serialised or not. """ if self.data_format == DATA_FORMAT_DICT: if force_dict_serialisation: serialize = True # If data format is DICT, we force serialisation to that format # unless overridden on input. value = self.user_attrs_list if self.output_repeated else self.user_attrs_dict # Special-case internal services that return metadata (e.g GetList-like ones) if self.zato_meta: # If search is provided, we need to first get output, # append the search the metadata and then serialise .. search = self.zato_meta.get('search') if search: output = self.sio.get_output(value, self.data_format, False) output['_meta'] = search return self.sio.serialise(output, self.data_format) # .. otherwise, with no search metadata provided, # we can data, serialised or not, immediately. return self.sio.get_output( value, self.data_format) if serialize else value else: out = self.sio.get_output(value, self.data_format) if serialize else value return out def append(self, value): self.user_attrs_list.append(value) self.output_repeated = True
def test_declare_c_types(n): """ >>> test_declare_c_types(0) >>> test_declare_c_types(1) >>> test_declare_c_types(2) """ # b00 = cython.declare(cython.bint, 0) b01 = cython.declare(cython.bint, 1) b02 = cython.declare(cython.bint, 2) # i00 = cython.declare(cython.uchar, n) i01 = cython.declare(cython.char, n) i02 = cython.declare(cython.schar, n) i03 = cython.declare(cython.ushort, n) i04 = cython.declare(cython.short, n) i05 = cython.declare(cython.sshort, n) i06 = cython.declare(cython.uint, n) i07 = cython.declare(cython.int, n) i08 = cython.declare(cython.sint, n) i09 = cython.declare(cython.slong, n) i10 = cython.declare(cython.long, n) i11 = cython.declare(cython.ulong, n) i12 = cython.declare(cython.slonglong, n) i13 = cython.declare(cython.longlong, n) i14 = cython.declare(cython.ulonglong, n) i20 = cython.declare(cython.Py_ssize_t, n) i21 = cython.declare(cython.size_t, n) # f00 = cython.declare(cython.float, n) f01 = cython.declare(cython.double, n) f02 = cython.declare(cython.longdouble, n)
class TrialRetriever: builDirector = cython.declare(BuildDirector) csvBuilder = cython.declare(ProductBuilder) def __init__(self): self._buildDirector = BuildDirector() self._dataBuilder = ProductBuilder() self.__loadBuilDirector() def __loadBuilDirector(self) -> None: self._buildDirector.builder = self._dataBuilder @cython.locals(dataName=cython.char, trialSize=cython.int) def callImgDataArr(self, dataName: str, trialSize: int = 15) -> Dict[str, ndarray]: data: List[ndarray] = None if (dataName == 'binocular_img'): data = self._buildDirector.buildImgArray(dataName) elif (dataName == 'scene_img'): data = self._buildDirector.buildImgArray(dataName) else: print('binocular_img or scene_img') raise AssertionError("this data names are not available") counterF: int = 1 counterT: int = 1 imgArrayDict: Dict[str, ndarray] = dict() currArr: ndarray = None for arr in data: for imgArr in arr: imgArrayDict["frame_" + str(counterF) + ', trial_' + str(counterT)] = imgArr counterF += 1 if (counterF >= trialSize + 1): counterF = 1 counterT += 1 return imgArrayDict @staticmethod @cython.locals(data=List[ndarray]) def __genImgArray(data: List[ndarray]) -> Generator: for arr in data: for imgArr in arr: yield imgArr @cython.locals(dataName=cython.char) def callTrialDataArr(self, dataName: str, stringArr: bool = True) -> ndarray: data: ndarray = self._buildDirector.buildCoordinateData(dataName) if (stringArr): return data else: currArr: ndarray = None row: str = None dataArr: List[ndarray] = list() for row in range(len(data)): currArr = self.__deleteFirstElement( self.__numpyStringToFloat(data[row])) dataArr.append(currArr) return asarray(dataArr) @cython.locals(data=cython.array, trialSize=cython.int) def callTrialDataArrAsDict(self, dataName: str, trialSize: int = 10) -> Dict[str, ndarray]: data: ndarray = self._buildDirector.buildCoordinateData(dataName) counterF: int = 1 counterT: int = 1 currRow: ndarray = None finalArr: Dict[str, ndarray] = dict() for row in range(len(data)): #currRow: ndarray = insert(data[row], 0, counter, axis=0); #finalArr.append(currRow); currRow = self.__deleteFirstElement( self.__numpyStringToFloat(data[row])) finalArr["frame_" + str(counterF) + ', trial_' + str(counterT)] = currRow counterF += 1 if (counterF >= trialSize + 1): counterF = 1 counterT += 1 return finalArr @staticmethod @cython.locals(oneDimArray=ndarray) def __numpyStringToFloat(oneDimArray: ndarray) -> ndarray: elem: str = None num: str = None floatingArr: ndarray = None try: return oneDimArray.astype(Float) except ValueError as e: try: for elem in range(len(oneDimArray)): floatingArr = asarray( [float(num) for num in oneDimArray[elem].split(",")], dtype=float32) except Exception as e: print(e) return floatingArr @staticmethod @cython.locals(oneDimArray=ndarray) def __deleteFirstElement(oneDimArray: ndarray) -> ndarray: return delete(oneDimArray, 0, None)
from __future__ import absolute_import import cython cython.declare(PyrexTypes=object, ExprNodes=object, Nodes=object, Builtin=object, InternalError=object, error=object, warning=object, py_object_type=object, unspecified_type=object, object_expr=object, fake_rhs_expr=object, TypedExprNode=object) from . import Builtin from . import ExprNodes from . import Nodes from . import Options from .PyrexTypes import py_object_type, unspecified_type from . import PyrexTypes from .Visitor import TreeVisitor, CythonTransform from .Errors import error, warning, InternalError from .Optimize import ConstantFolding class TypedExprNode(ExprNodes.ExprNode): # Used for declaring assignments of a specified type without a known entry.
def generateClarStructures(mol): """ Generate Clar structures for a given molecule. Returns a list of :class:`Molecule` objects corresponding to the Clar structures. """ cython.declare(output=list, molList=list, newmol=Molecule, aromaticRings=list, bonds=list, solution=list, y=list, x=list, index=cython.int, bond=Bond, ring=list) if not mol.isCyclic(): return [] try: output = _clarOptimization(mol) except ILPSolutionError: # The optimization algorithm did not work on the first iteration return [] molList = [] for newmol, aromaticRings, bonds, solution in output: # The solution includes a part corresponding to rings, y, and a part corresponding to bonds, x, using # nomenclature from the paper. In y, 1 means the ring as a sextet, 0 means it does not. # In x, 1 corresponds to a double bond, 0 either means a single bond or the bond is part of a sextet. y = solution[0:len(aromaticRings)] x = solution[len(aromaticRings):] # Apply results to molecule - double bond locations first for index, bond in enumerate(bonds): if x[index] == 0: bond.order = 1 # single elif x[index] == 1: bond.order = 2 # double else: raise ValueError( 'Unaccepted bond value {0} obtained from optimization.'. format(x[index])) # Then apply locations of aromatic sextets by converting to benzene bonds for index, ring in enumerate(aromaticRings): if y[index] == 1: _clarTransformation(newmol, ring) try: newmol.updateAtomTypes() except AtomTypeError: pass else: molList.append(newmol) return molList