diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8dd991e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+# Cache-
+*.pyc
+
+# Pycharm editor-
+*idea
+
+# Distrubtion/Packaging-
+build/
+dist/
+*.egg-info/
+pypirc
+
+# Pytest
+.pytest_cache
\ No newline at end of file
diff --git a/Carbonpy.py b/Carbonpy.py
deleted file mode 100644
index b3169bc..0000000
--- a/Carbonpy.py
+++ /dev/null
@@ -1,186 +0,0 @@
-# An organic chemistry module for Grade 12(mostly)-
-# Can name compounds based on their structure, convert the compound from one functional group to another and more...
-# - single bond
-# = double bond
-# ~ triple bond
-# TODO: Identify branched chains, functional groups and somehow represent the compound in the same way you would draw it
-
-import re
-from typing import Union
-
-
-class Namer(object): # IUPAC Names for now only
- symbol = '\u2261' # The triple bond symbol ≡
- subscripts = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉") # Subscripts for molecular and structural formula
-
- def __init__(self, structure: str) -> None:
- self.processing = self.structure = structure # Processing is a string only for processing
- self.carbons = 0 # No. of carbon atoms present
- self.hydrogens = 0
- self.bond = "" # Name of bond
- # self.final = "" # Name of final compound
-
- # Counts number of hydrogens and carbons in compound-
- self.carbons = self.atom_counter('C')
- self.hydrogens = self.atom_counter('H')
-
- def __str__(self): # If user wants to see structural formula
- return f"{self.structure.replace('~', self.symbol).translate(self.subscripts)}"
-
- def __repr__(self):
- return f"{self.__class__.__name__}({self.structure!r})"
-
- def molecular_formula(self): # If user wants to see molecular formula
- return str(f"C{self.carbons if self.carbons > 1 else ''}H{self.hydrogens}").translate(self.subscripts)
-
- def analyser(self) -> str:
- compound_name = ""
- many_bonds = "" # Is empty for saturated compounds
-
- # Checks valencies of atoms in compound-
- self.valency_checker()
- # Processing and deciding name(s) of the compound-
- bond_type = self.suffix_namer()
-
- if any(suffix in bond_type for suffix in list(multipl_suffixes.values())):
- many_bonds += "a-" # This is the 'a' in a compound like butadiene
- elif not bond_type == "ane": # If compound has only one unsaturated bond
- many_bonds += "-"
- compound_name += f"{many_bonds}{bond_type}" # Suffix and position is decided
-
- return f"{prefixes[self.carbons].capitalize()}{compound_name}" # returns final name
-
- def valency_checker(self) -> None:
- """Checks if valencies of carbon are satisfied and raises error if not satisfied. """
-
- valency = 0
- hydros_bonds = {'H': 1, "H2": 1, "H3": 2, "H4": 3, '-': 1, '=': 2, '~': 3}
- splitted = re.split('([-=~])', self.structure) # Splits the bonds and elements
-
- for index, element in enumerate(splitted): # Adds the bonds to the string of atoms
- if element == "-" or element == "=" or element == "~":
- splitted[index - 1] += element
- splitted[index + 1] += element
- splitted.pop(index) # Removes those bonds from the list. Final list example: ['CH3-', 'CH2-', 'CH3-']
-
- for element in splitted: # Counts the bonds and hydrogens to see if valency is satisfied
- for hyd_bonds in hydros_bonds.keys(): # Iterating through dict
- if hyd_bonds in element:
- valency += hydros_bonds[hyd_bonds] * element.count(hyd_bonds)
- if valency != 4:
- raise ValencyError("Check valencies of your compound!")
- valency = 0
-
- def atom_counter(self, element):
- if element == "C":
- return self.structure.count('C')
-
- elif element == "H":
- count = 0
- hydros = {"H": 1, "H2": 1, "H3": 2, "H4": 3} # Each value is less than 1 of parent since 'H' is in it too.
- for hydro, value in hydros.items():
- count += self.structure.count(hydro) * value # Multiplied by its value to get actual value of H
- return count
-
- def suffix_namer(self) -> str:
- lowest_db = lowest_tb = db_suffix = tb_suffix = "" # db,tb- double, triple bond
- self.processing = self.processing.translate({ord(i): None for i in 'CH23'}) # Removes everything except bonds
-
- lows_pos = self.lowest_position()
- if not isinstance(lows_pos, dict): # If compound is saturated
- return f"ane" # Alkane
-
- for key, value in lows_pos.items():
- if value == '=':
- lowest_db += f"{key}," # Adds position of double bond with ',' for more bonds
- elif value == '~':
- lowest_tb += f"{key}," # Same as above, except this time for triple bond
-
- lowest_tb = lowest_tb.strip(',') # Removes ','
- lowest_db = lowest_db.strip(',')
-
- # If many double/triple bonds present, get their suffix(di, tri, tetra, etc.)
- if len(lowest_db) >= 3:
- db_suffix = f"-{multipl_suffixes[len(lowest_db.replace(',', ''))]}" # Add that '-' too
- else:
- db_suffix += "-" # else only '-'
-
- if len(lowest_tb) >= 3:
- tb_suffix = f"-{multipl_suffixes[len(lowest_tb.replace(',', ''))]}"
- else:
- tb_suffix += "-"
-
- if '=' in self.processing and '~' in self.processing: # If double and triple bond present
- return f"{lowest_db}{db_suffix}en-{lowest_tb}{tb_suffix}yne"
-
- elif '~' in self.processing: # Only triple bond present
- return f"{lowest_tb}{tb_suffix}yne"
-
- elif '=' in self.processing: # Only double bond present
- return f"{lowest_db}{db_suffix}ene" # Return with di,tri,etc
-
- def lowest_position(self) -> Union[None, dict]:
- """First point of difference rule used"""
- lowest_front = {}
- lowest_back = {}
- # TODO: Maybe number from front and back simultaneously? (Also made me realize this may not work for isomers)
- # Adds all occurrences from front
- for index, string in enumerate(self.processing):
- if string in ('=', '~'):
- lowest_front[index + 1] = string
-
- # Adds all occurrences from back
- for index, string in enumerate(''.join(reversed(self.processing))):
- if string in ('=', '~'):
- lowest_back[index + 1] = string
-
- assert (len(lowest_front) == len(lowest_back)) # Make sure they have the same length
- for (index, value), (index2, value2) in zip(lowest_front.items(), lowest_back.items()):
- # First point of difference-
- if index < index2:
- return lowest_front
- elif index2 < index:
- return lowest_back
- elif index == index2: # Same index, check for precedence (only = and ~ for now)
- # Double bond has more precedence than triple
- if value == '=': # Will change into a dict access for func groups priority
- return lowest_front
- elif value2 == '=':
- return lowest_back
-
- if len(lowest_front) == 0:
- return None
- else:
- return lowest_back # Can also return front(if compound is symmetrical)
-
- def priority_order(self):
- pass
-
-
-class ValencyError(Exception):
- pass
-
-
-prefixes = {1: "meth", 2: "eth", 3: "prop", 4: "but", 5: "pent", 6: "hex", 7: "hept", 8: "oct", 9: "non", 10: "dec",
- 11: "undec", 12: "dodec", 13: "tridec", 14: "tetradec", 15: "pentadec", 16: "hexadec", 17: "heptadec",
- 18: "octadec", 19: "nonadec", 20: "icos"}
-
-# precedence = {"=": 1, "~": 1}
-
-multipl_suffixes = {2: "di", 3: "tri", 4: "tetra", 5: "penta", 6: "hexa", 7: "hepta", 8: "octa", 9: "nona"}
-
-compound1 = Namer('CH3-C~C-CH3')
-compound2 = Namer('CH~CH')
-compound3 = Namer('CH~C-C~C-CH=C=C=CH2')
-compound4 = Namer('CH4')
-compound5 = Namer('CH2=CH-CH=CH-CH=CH2')
-compound6 = Namer('CH2=CH2')
-compound7 = Namer('CH~C-CH=CH2')
-
-print(f"{compound1}\n{compound1.molecular_formula()}\n{compound1.analyser()}\n")
-print(f"{compound2}\n{compound2.molecular_formula()}\n{compound2.analyser()}\n")
-print(f"{compound3}\n{compound3.molecular_formula()}\n{compound3.analyser()}\n")
-print(f"{compound4}\n{compound4.molecular_formula()}\n{compound4.analyser()}\n")
-print(f"{compound5}\n{compound5.molecular_formula()}\n{compound5.analyser()}\n")
-print(f"{compound6}\n{compound6.molecular_formula()}\n{compound6.analyser()}\n")
-print(f"{compound7}\n{compound7.molecular_formula()}\n{compound7.analyser()}\n")
diff --git a/LICENSE b/LICENSE
index 261eeb9..29f81d8 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,201 +1,201 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
index 4386812..f928005 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,37 @@
-# Carbonpy
-A module which names straight/branched chain organic compounds, suggests conversions from one type to another, etc.
-
-## Usage-
-
-### Naming compounds-
-Instantiate the class `Namer()` , which takes a string which contains the hydrocarbon (condensed form) and then call it with a method named `analyser()` to get the IUPAC name of the compound.
-
-Example:
-```
-a = Namer('CH~CH')
-a.analyser()
->>> Ethyne
-```
-
-Due to limitations in expressing a hydrocarbon easily, we have selected this path
-Single bond:- -
-Double bond:- =
-Triple bond:- ~
-Branches:- -([compound](more branches...))([another branch from same carbon])- and so on
-(Branches support coming soon)
-
-You can also get the molecular formula of the compound:
-```
-compound = Namer('CH~C-C~C-CH=C=C=CH2')
-compound.molecular_formula()
->>> C₈H₄
-```
-Example- 2-Methylpropane would be expressed as:
-```
-a = Namer('CH3-CH(CH3)-CH3').analyser()
->>> 2-Methylpropane
-```
-
-Will support naming with functional groups in the future.
-
-P.S: This project is NOT dead.
+# Carbonpy
+A module which names straight/branched chain organic compounds, suggests conversions from one type to another, etc.
+
+## Usage-
+
+### Naming compounds-
+Instantiate the class `Namer()` , which takes a string which contains the hydrocarbon (condensed form) and then call it with a method named `analyser()` to get the IUPAC name of the compound.
+
+Example:
+```
+a = Namer('CH~CH')
+a.analyser()
+>>> Ethyne
+```
+
+Due to limitations in expressing a hydrocarbon easily, we have selected this path
+Single bond:- -
+Double bond:- =
+Triple bond:- ~
+Branches:- -([compound](more branches...))([another branch from same carbon])- and so on
+(Branches support coming soon)
+
+You can also get the molecular formula of the compound:
+```
+compound = Namer('CH~C-C~C-CH=C=C=CH2')
+compound.molecular_formula()
+>>> C₈H₄
+```
+Example- 2-Methylpropane would be expressed as:
+```
+a = Namer('CH3-CH(CH3)-CH3').analyser()
+>>> 2-Methylpropane
+```
+
+Will support naming with functional groups in the future.
+
+P.S: This project is NOT dead.
diff --git a/carbonpy/__init__.py b/carbonpy/__init__.py
new file mode 100644
index 0000000..cf6be27
--- /dev/null
+++ b/carbonpy/__init__.py
@@ -0,0 +1,9 @@
+from namer import BaseNamer, Branched
+from base.compound import CompoundObject
+from .base.element import Element
+from error import ValencyError
+from .version import __version__
+
+__author__ = 'Harshil Mehta'
+
+__all__ = ['BaseNamer', 'ValencyError', 'Branched', 'Element', 'CompoundObject']
diff --git a/carbonpy/base/GraphEdge.py b/carbonpy/base/GraphEdge.py
new file mode 100644
index 0000000..e7ce9ce
--- /dev/null
+++ b/carbonpy/base/GraphEdge.py
@@ -0,0 +1,5 @@
+class GraphEdge:
+ def __init__(self, from_node, to_node, bond_type):
+ self.from_node = from_node
+ self.to_node = to_node
+ self.bond_type = bond_type
diff --git a/carbonpy/base/GraphElement.py b/carbonpy/base/GraphElement.py
new file mode 100644
index 0000000..06ed20a
--- /dev/null
+++ b/carbonpy/base/GraphElement.py
@@ -0,0 +1,40 @@
+from carbonpy import Element
+from ..graphical import forces
+
+from math import cos, sin
+
+
+class GraphElement(Element):
+ def __init__(self, value, comp, x, y):
+ super().__init__(value, comp)
+ self.x = x
+ self.y = y
+
+ def get_coords(self):
+ return self.x, self.y
+
+ def calc_attractive_f_mag(self, other):
+ dist = forces.calc_dist(other, self)
+ return 50 * (dist - 65)
+
+ def calc_attractive_f(self, other):
+ force_mag = self.calc_attractive_f_mag(other)
+ force_angle = forces.calc_angle(other, self)
+
+ force_x = cos(force_angle) * force_mag
+ force_y = sin(force_angle) * force_mag
+ return force_x, force_y
+
+ def calc_repulsive_f_mag(self, other):
+ dist = forces.calc_dist(other, self)
+ if dist < 100:
+ dist = 100
+ return 10000 * (10 ** 10) / (dist ** dist)
+
+ def calc_repulsive_f(self, other):
+ force_mag = self.calc_repulsive_f_mag(other)
+ force_angle = forces.calc_angle(other, self)
+
+ force_x = cos(force_angle) * force_mag
+ force_y = sin(force_angle) * force_mag
+ return force_x, force_y
diff --git a/carbonpy/base/__init__.py b/carbonpy/base/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/carbonpy/base/compound.py b/carbonpy/base/compound.py
new file mode 100644
index 0000000..9346f7e
--- /dev/null
+++ b/carbonpy/base/compound.py
@@ -0,0 +1,193 @@
+# An organic chemistry module for Grade 12(mostly)-
+# Can name compounds based on their structure, convert the compound from one functional group to another and more...
+# - single bond
+# = double bond
+# ~ triple bond
+# TODO: Identify functional groups and somehow represent the compound in the same way you would draw it
+# TODO: fix imports by adding '.' before merging
+
+from collections import deque
+from typing import Dict, Deque
+
+from constants import symbol
+from base.element import Element
+from error import ValencyError
+
+
+class CompoundObject: # IUPAC Names for now only
+ subscripts = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉") # Subscripts for molecular and structural formula
+
+ def __init__(self, structure: str) -> None:
+ self.processing = self.structure = structure.upper() # Processing is a string only for processing
+ self._carbons = self.atom_counter('C')
+
+ if self._carbons > 10000:
+ raise ValueError(f"Got {self._carbons} carbon atoms, this version supports only up to 10,000 carbon atoms!")
+
+ self._hydrogens = self.atom_counter('H')
+
+ self._carbon_comps = self.processing.translate({ord(i): ' ' for i in '-=~()'}).split()
+ self._bonds_only = list(self.processing.translate({ord(i): None for i in 'CH234()'}))
+ # assert len(self._carbon_comps) - 1 == len(self._bonds_only)
+
+ self._graph: Dict[Element, Deque[str]] = self.make_graph()
+
+ def __str__(self): # If user wants to see structural formula; called from print()
+ return f"{self.structure.replace('~', symbol).translate(self.subscripts)}"
+
+ def __repr__(self):
+ return f"{self.__class__.__name__}({self.structure!r})"
+
+ def __len__(self) -> int:
+ return self.carbons
+
+ def __iter__(self) -> Element:
+ for element_node in self._graph:
+ yield element_node
+
+ def __eq__(self, other):
+ other = other.molar_mass if isinstance(other, self.__class__) else other
+ return self.molar_mass == float(other) if isinstance(other, (int, float)) else NotImplemented
+
+ def __lt__(self, other):
+ other = other.molar_mass if isinstance(other, self.__class__) else other
+ return self.molar_mass < float(other) if isinstance(other, (int, float)) else NotImplemented
+
+ def __le__(self, other):
+ other = other.molar_mass if isinstance(other, self.__class__) else other
+ return self.molar_mass <= float(other) if isinstance(other, (int, float)) else NotImplemented
+
+ def __gt__(self, other):
+ other = other.molar_mass if isinstance(other, self.__class__) else other
+ return self.molar_mass > float(other) if isinstance(other, (int, float)) else NotImplemented
+
+ def __ge__(self, other):
+ other = other.molar_mass if isinstance(other, self.__class__) else other
+ return self.molar_mass >= float(other) if isinstance(other, (int, float)) else NotImplemented
+
+ @property
+ def graph(self) -> Dict[Element, Deque[str]]:
+ return self._graph
+
+ @property
+ def molar_mass(self) -> float:
+ molar_masses = {'C': 12.0107, 'H': 1.00784}
+ return molar_masses['C'] * self.carbons + molar_masses['H'] * self.hydrogens
+
+ @property
+ def carbons(self) -> int:
+ return self._carbons
+
+ @property
+ def hydrogens(self) -> int:
+ return self._hydrogens
+
+ def molecular_formula(self) -> str: # If user wants to see molecular formula
+ return str(f"C{self.carbons if self.carbons > 1 else ''}H{self.hydrogens}").translate(self.subscripts)
+
+ @staticmethod
+ def _remove_bonds(string: str) -> str:
+ return string.translate({ord(i): ' ' for i in '-=~'})
+
+ @staticmethod
+ def add_edge(obj: dict, prev_element: str, this_element: str):
+ obj.setdefault(prev_element, deque([]))
+ obj.setdefault(this_element, deque([]))
+
+ obj[prev_element].append(this_element)
+ obj[this_element].append(prev_element)
+ return obj
+
+ def valency_checker(self) -> bool:
+ """Checks if valencies of carbon are satisfied and raises error if not satisfied."""
+ carbon_index = 0
+ val = {'-': 1, '=': 2, '~': 3, '': 0}
+ for e in self.graph:
+ valency = val[e.back_bond] + val[e.top_bond] + val[e.bottom_bond] + val[e.front_bond] + e.hydrogens()
+ carbon_index = self.structure.find('C', carbon_index) + 1
+ if valency != 4:
+ raise ValencyError(f"Check valencies of your compound!\n{self.structure}\n{' ' * (carbon_index - 1)}^")
+ return True
+
+ def atom_counter(self, element):
+ if element.upper() == "C":
+ return self.structure.count('C')
+
+ elif element.upper() == "H":
+ count = 0
+ hydros = {"H": 1, "H2": 1, "H3": 2, "H4": 3} # Each value is less than 1 of parent since 'H' is in it too.
+ for hydro, value in hydros.items():
+ count += self.structure.count(hydro) * value # Multiplied by its value to get actual value of H
+ return count
+ raise ValueError(f"Got {element}. Only Carbon ('C') and Hydrogen ('H') is supported in this version!")
+
+ def to_element(self, graph: dict):
+ _graph = {}
+ for element_node, connected_nodes in graph.items():
+ element = Element(value=element_node, comp=self._carbon_comps[int(element_node[1:]) - 1])
+
+ position = max(0, int(element_node[1:]) - 2)
+ element.back_bond = self._bonds_only[position]
+
+ for node, at in zip(range(1, len(connected_nodes)), {'front_bond', 'top_bond', 'bottom_bond'}):
+ position = int(connected_nodes[node][1:]) - 2
+ setattr(element, at, self._bonds_only[position])
+
+ _graph[element] = connected_nodes
+ return _graph
+
+ def make_graph(self):
+ _graph: Dict[str, Deque[str]] = {}
+ to_repl = {'-': ' ', '=': ' ', '~': ' ', 'C(': 'C (', 'H(': 'H (', 'H)': 'H )', 'H2)': 'H2 )', 'H3)': 'H3 )',
+ ')(': ') ('}
+
+ splitted = self.structure
+
+ branch_elements = deque([])
+ visited = deque(['C1'])
+ carbon_indexes = []
+ index = 0
+
+ for k, v in to_repl.items():
+ splitted = splitted.replace(k, v)
+ splitted = splitted.split()
+ previous = splitted[0]
+
+ for ele in splitted:
+ if 'C' in ele:
+ index += 1
+ carbon_indexes.append(index)
+
+ for index, element in zip(carbon_indexes[1:], splitted[1:]):
+ if previous != ')' and element == '(':
+ branch_elements.extend([visited[-1]] * 3)
+ previous = element
+ continue
+
+ if previous == ")" and element != '(':
+ popped1 = branch_elements.pop()
+ _graph = self.add_edge(_graph, popped1, f"C{index}")
+ if branch_elements and popped1 == branch_elements[-1]:
+ branch_elements.pop()
+
+ elif previous == ")" and element == "(":
+ _graph = self.add_edge(_graph, branch_elements.pop(), f"C{index + 1}")
+
+ elif previous == "(" and f"C{index}" not in _graph:
+ _graph = self.add_edge(_graph, branch_elements.pop(), f"C{index}")
+
+ elif previous != "(" and element != ')':
+ _graph = self.add_edge(_graph, visited[-1], f"C{index}")
+
+ visited.append(f"C{index}")
+ previous = element
+
+ _graph: Dict[Element, Deque[str]] = self.to_element(_graph) # Convert string nodes to Element nodes
+ return _graph
+
+# a = CompoundObject(structure="CH3-C(=CH-C~CH)(-CH=CH2)-C~CH")
+# a = CompoundObject(structure="CH~C-CH(-C(-CH3)(-CH2-CH3)-CH3)-C(-CH=CH(-CH2-CH3)-CH3)(-CH3)-CH2-CH3")
+#
+# print(a.carbons)
+# for element in a:
+# print(element)
diff --git a/carbonpy/base/element.py b/carbonpy/base/element.py
new file mode 100644
index 0000000..7a59fef
--- /dev/null
+++ b/carbonpy/base/element.py
@@ -0,0 +1,33 @@
+class Element(object): # Node
+ __slots__ = ('value', 'comp', 'front_bond', 'back_bond', 'top_bond', 'bottom_bond')
+
+ def __init__(self, value: int or str, comp: str) -> None:
+ # Required-
+ self.value: int or str = value
+ self.comp: str = comp
+
+ # Optional-
+ self.front_bond: str = ''
+ self.back_bond: str = ''
+ self.top_bond: str = ''
+ self.bottom_bond: str = ''
+
+ def __str__(self) -> str:
+ return self.comp
+
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}({self.value!r}, {self.comp!r})"
+
+ def __hash__(self) -> hash:
+ return hash(self.value)
+
+ def __eq__(self, other) -> bool:
+ if isinstance(other, str):
+ return self.value == other
+ return self.value == other.value if isinstance(other, Element) else NotImplemented
+
+ def hydrogens(self) -> int:
+ last = self.comp[-1]
+ if last.isdigit():
+ return int(last)
+ return 1 if last == 'H' else 0
diff --git a/carbonpy/constants.py b/carbonpy/constants.py
new file mode 100644
index 0000000..83fad40
--- /dev/null
+++ b/carbonpy/constants.py
@@ -0,0 +1,8 @@
+# prefixes = {1: "meth", 2: "eth", 3: "prop", 4: "but", 5: "pent", 6: "hex", 7: "hept", 8: "oct", 9: "non", 10: "dec",
+# 11: "undec", 12: "dodec", 13: "tridec", 14: "tetradec", 15: "pentadec", 16: "hexadec", 17: "heptadec",
+# 18: "octadec", 19: "nonadec", 20: "icos"}
+
+# precedence = {"=": 1, "~": 1}
+
+multipl_suffixes = {2: "di", 3: "tri", 4: "tetra", 5: "penta", 6: "hexa", 7: "hepta", 8: "octa", 9: "nona"}
+symbol = '\u2261' # The triple bond symbol ≡
diff --git a/carbonpy/error.py b/carbonpy/error.py
new file mode 100644
index 0000000..2295c1e
--- /dev/null
+++ b/carbonpy/error.py
@@ -0,0 +1,2 @@
+class ValencyError(Exception):
+ pass
diff --git a/carbonpy/examples.py b/carbonpy/examples.py
new file mode 100644
index 0000000..0e18104
--- /dev/null
+++ b/carbonpy/examples.py
@@ -0,0 +1,34 @@
+from namer import Branched, BaseNamer
+
+# Examples-
+
+# comp1 = BaseNamer('CH2=C=C=C=CH2')
+# comp2 = BaseNamer('CH~C-CH3')
+# comp3 = BaseNamer('CH~C-C~C-CH=C=C=CH2')
+# comp4 = BaseNamer('CH4')
+# comp5 = BaseNamer('CH2=CH-CH=CH-CH=CH2')
+# comp6 = BaseNamer('CH2=CH2')
+# comp7 = BaseNamer('CH~C-CH=CH2')
+# comp_tmp = Branched('CH4')
+# comp_tmp2 = Branched('CH3-CH3')
+comp8 = Branched('CH3-CH(-CH3)-CH3')
+comp9 = Branched('CH3-C(-CH=C=CH2)(-CH3)-CH3')
+comp10 = Branched('CH3-C(-CH2-CH2-CH3)(-CH2-CH2-CH3)-CH3')
+comp11 = Branched('CH~C-CH(-C(-CH3)(-CH2-CH3)-CH3)-C(-CH=C(-CH2-CH3)-CH3)(-CH3)-CH2-CH3')
+# comp12 = Branched('CH3-C(-CH2-CH(-CH3)-CH3)(-CH3)-CH2-CH3')
+
+# comp10 = Branched('CH3-CH(-CH2-CH3)-CH3') # 2-methylbutane. This shows user can input structure in any order!
+# print(f"func :{comp8.branch_splitter()}")
+# print()
+# a = comp8.determine_longest()
+# print()
+# b = comp9.determine_longest()
+# print()
+# c = comp11.determine_longest()
+# print()
+comp11.valency_checker()
+
+# comps = [comp1, comp2, comp3, comp4, comp5, comp6, comp7]
+
+# for comp in comps:
+# print(f"{comp}\n{comp.molecular_formula()}\n{comp.analyser()}\n")
diff --git a/carbonpy/graphical/__init__.py b/carbonpy/graphical/__init__.py
new file mode 100644
index 0000000..bd38086
--- /dev/null
+++ b/carbonpy/graphical/__init__.py
@@ -0,0 +1,2 @@
+from .playground import Playground
+from .interface import Interface
diff --git a/carbonpy/graphical/builder.py b/carbonpy/graphical/builder.py
new file mode 100644
index 0000000..2beff4e
--- /dev/null
+++ b/carbonpy/graphical/builder.py
@@ -0,0 +1,4 @@
+from gi.repository import Gtk
+
+builder = Gtk.Builder()
+builder.add_from_file("gui.glade")
diff --git a/carbonpy/graphical/css_styles/canvas_style.css b/carbonpy/graphical/css_styles/canvas_style.css
new file mode 100644
index 0000000..753712c
--- /dev/null
+++ b/carbonpy/graphical/css_styles/canvas_style.css
@@ -0,0 +1,44 @@
+/* Setting carbon canvas and its children's properties- */
+@define-color grey #C6C6C6;
+@define-color black #000000;
+@define-color dark_grey #A4A4A4;
+@define-color off_white #DDDEE7;
+
+
+#carboncanvas {
+ background-color: @off_white;
+ font-size: 1em;
+ border-radius: 1em;
+}
+
+#carboncanvas button {
+ background: none;
+ background-image: none;
+ color: @black;
+ border-radius: 10em;
+ padding: 0.2em;
+ margin: 0px;
+}
+
+#carboncanvas button:hover{
+ background: none;
+ background-color: @grey;
+ font-size: 1em;
+}
+
+#carboncanvas button:active{
+ background-color: @dark_grey;
+}
+
+#carboncanvas button.bond {
+ border-radius: 10em;
+ padding: 0.2em;
+ margin: 0px;
+}
+
+#carboncanvas .bond:hover {
+ transition-duration: 0s;
+ background: none;
+ border-width: 0px;
+ font-weight: bold;
+}
diff --git a/carbonpy/graphical/css_styles/styles.css b/carbonpy/graphical/css_styles/styles.css
new file mode 100644
index 0000000..7ffb743
--- /dev/null
+++ b/carbonpy/graphical/css_styles/styles.css
@@ -0,0 +1,2 @@
+@import "canvas_style.css";
+@import "test.css";
\ No newline at end of file
diff --git a/carbonpy/graphical/css_styles/test.css b/carbonpy/graphical/css_styles/test.css
new file mode 100644
index 0000000..4ea7d84
--- /dev/null
+++ b/carbonpy/graphical/css_styles/test.css
@@ -0,0 +1,13 @@
+/* General */
+#event_label:hover #test_label{
+ background: none;
+ background-color: red;
+ color: blue;
+ font: 10px "Comic Sans";
+}
+
+
+button:hover {
+ background: none;
+ background-color: red;
+}
diff --git a/carbonpy/graphical/forces.py b/carbonpy/graphical/forces.py
new file mode 100644
index 0000000..8867a55
--- /dev/null
+++ b/carbonpy/graphical/forces.py
@@ -0,0 +1,15 @@
+from math import atan2, hypot
+
+
+def dist_diff(x2, y2, x1, y1):
+ return x2 - x1, y2 - y1
+
+
+def calc_dist(node2, node1):
+ adj, opp = dist_diff(node2.x, node2.y, node1.x, node1.y)
+ return hypot(adj, opp)
+
+
+def calc_angle(node2, node1):
+ adj, opp = dist_diff(node2.x, node2.y, node1.x, node1.y)
+ return atan2(opp, adj)
diff --git a/carbonpy/graphical/gui.glade b/carbonpy/graphical/gui.glade
new file mode 100644
index 0000000..3ebbfc1
--- /dev/null
+++ b/carbonpy/graphical/gui.glade
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
diff --git a/carbonpy/graphical/gui.glade~ b/carbonpy/graphical/gui.glade~
new file mode 100644
index 0000000..2546dfd
--- /dev/null
+++ b/carbonpy/graphical/gui.glade~
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+ False
+ Carbonpy
+ 1344
+ 768
+
+
+
+
+
+
+ True
+ False
+
+
+ wow
+ 64
+ 30
+ True
+ True
+ True
+
+
+ 820
+ 494
+
+
+
+
+ event_label
+ 100
+ 80
+ True
+ False
+ GDK_POINTER_MOTION_MASK | GDK_STRUCTURE_MASK
+
+
+ test_label
+ 53
+ 52
+ True
+ False
+ test
+
+
+
+
+ 713
+ 263
+
+
+
+
+ carboncanvas
+ 634
+ 321
+ True
+ True
+ GDK_POINTER_MOTION_MASK | GDK_STRUCTURE_MASK
+ in
+
+
+
+ True
+ False
+ 0
+
+
+ True
+ False
+
+
+ CH4
+ C1
+ 38
+ 32
+ True
+ False
+ True
+ none
+
+
+
+
+
+ 278
+ 142
+
+
+
+
+
+
+
+
+ 16
+ 100
+
+
+
+
+
+
diff --git a/carbonpy/graphical/interface.py b/carbonpy/graphical/interface.py
new file mode 100644
index 0000000..39977e5
--- /dev/null
+++ b/carbonpy/graphical/interface.py
@@ -0,0 +1,34 @@
+from gi.repository import Gtk, Gdk
+
+
+class Interface:
+
+ @staticmethod
+ def on_enter_notify_event(widget, event):
+ # print('hovering carbons')
+ widget.get_window().set_cursor(Gdk.Cursor.new_from_name(display=widget.get_display(), name="pointer"))
+
+ @staticmethod
+ def on_leave_notify_event(widget, event):
+ # print("HOVEE leave")
+ widget.get_window().set_cursor(cursor=None)
+
+ @staticmethod
+ def on_carboncanvas_enter_notify_event(widget, event):
+ # print('carboncanvas hover')
+ # Set 'grab' cursor-
+ cursor = Gdk.Cursor.new_from_name(display=widget.get_display(), name="hand1") # TODO: Revert back to 'grab'
+ widget.get_window().set_cursor(cursor)
+
+
+# def on_button_clicked(button):
+# print("Clicked!!")
+#
+#
+# def on_event_label_button_press_event(widget, event):
+# pass # print(widget)
+#
+#
+# def on_event_label_motion_notify_event(widget, event):
+# widget.set_state_flags(Gtk.StateFlags.PRELIGHT, clear=True)
+
diff --git a/carbonpy/graphical/main_ui.py b/carbonpy/graphical/main_ui.py
new file mode 100644
index 0000000..332da8f
--- /dev/null
+++ b/carbonpy/graphical/main_ui.py
@@ -0,0 +1,34 @@
+try:
+ import gi
+ gi.require_version("Gtk", "3.0")
+ from gi.repository import Gtk, Gdk
+except ModuleNotFoundError:
+ raise ModuleNotFoundError("PyGObject isn't installed on your machine. Run pip install PyGObject")
+
+from interface import Interface
+from playground import Playground
+from builder import builder
+
+
+handlers = {
+ "on_window1_destroy": Gtk.main_quit,
+ # "on_button_clicked": Interface.on_button_clicked,
+ "on_enter_notify_event": Interface.on_enter_notify_event,
+ "on_leave_notify_event": Interface.on_leave_notify_event,
+ # "on_event_label_button_press_event": on_event_label_button_press_event,
+ # "on_event_label_motion_notify_event": on_event_label_motion_notify_event,
+ "on_carbon_clicked": Playground().on_carbon_clicked,
+ "on_carboncanvas_enter_notify_event": Interface.on_carboncanvas_enter_notify_event,
+}
+
+builder.connect_signals(handlers)
+
+cssProvider = Gtk.CssProvider()
+cssProvider.load_from_path('./css_styles/styles.css')
+screen = Gdk.Screen().get_default()
+Gtk.StyleContext().add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
+
+window = builder.get_object("window1")
+window.show_all()
+
+Gtk.main()
diff --git a/carbonpy/graphical/playground.py b/carbonpy/graphical/playground.py
new file mode 100644
index 0000000..d9f582e
--- /dev/null
+++ b/carbonpy/graphical/playground.py
@@ -0,0 +1,131 @@
+# Class to handle addition of carbons, bonds, etc in carboncanvas.
+from gi.repository import Gtk
+
+from interface import Interface
+from builder import builder
+from carbonpy.carbonpy.base.GraphElement import GraphElement
+
+
+class Playground:
+ graph = {}
+ total_carbons = 1
+
+ fixed_canvas = builder.get_object("fixedcanvas")
+
+ carbon_buttons: list = fixed_canvas.get_children()
+ this_node, next_node = None, None
+
+ def on_carbon_clicked(self, button):
+ # bonds: ‒, =, ≡
+ self.this_node = int(button.get_name())
+
+ x, y = self.fixed_canvas.child_get(button, "x", "y")
+ print(f"{x=},{y=}")
+
+ molecule = GraphElement(value=self.this_node, comp=button.get_label(), x=x, y=y)
+
+ if not self.graph:
+ self.graph[molecule] = []
+
+ self.do_physics()
+
+ # space_available, to_place_x, to_place_y = self.get_adjacent_atoms(button, (x, y), self.fixed_canvas)
+ # print(space_available, to_place_x, to_place_y)
+ #
+ # if not space_available:
+ # return
+
+ self.total_carbons += 1
+
+ self.next_node = self.total_carbons
+ connections = self.graph[self.this_node]
+
+ if not connections:
+ carbon_label = "CH3"
+ button.set_label("CH3")
+ elif len(connections) == 1:
+ carbon_label = "CH2"
+ button.set_label(carbon_label)
+ elif len(connections) == 2:
+ carbon_label = "CH"
+ button.set_label(carbon_label)
+ elif len(connections) == 3:
+ carbon_label = "C"
+ button.set_label(carbon_label)
+ else:
+ return
+
+ bond_button = self.make_bond_button(None, '‒')
+ carbon_button = self.make_carbon_button(carbon_label)
+
+ self.set_properties(bond_button, carbon_button)
+
+ # print(self.graph)
+ self.fixed_canvas.put(bond_button, x + 30, y)
+ self.fixed_canvas.put(carbon_button, to_place_x, to_place_y)
+
+ # assert len(self.carbon_buttons) == self.total_carbons
+
+ self.carbon_buttons[-1].set_label('CH3') # Terminal carbon should always be CH3
+ print()
+
+ def make_bond_button(self, widget, bond_type: str):
+ bond_button = Gtk.Button(label=bond_type, name="single", relief=Gtk.ReliefStyle.NONE)
+
+ bond_button.connect("enter-notify-event", Interface.on_enter_notify_event)
+ bond_button.connect("leave-notify-event", Interface.on_leave_notify_event)
+
+ Gtk.StyleContext.add_class(bond_button.get_style_context(), "bond")
+
+ return bond_button
+
+ def make_carbon_button(self, actual_rep: str):
+ carbon_button = Gtk.Button(label=actual_rep, name=self.next_node, relief=Gtk.ReliefStyle.NONE)
+ carbon_button.connect("clicked", self.on_carbon_clicked)
+ carbon_button.connect("enter-notify-event", Interface.on_enter_notify_event)
+ carbon_button.connect("leave-notify-event", Interface.on_leave_notify_event)
+
+ self.carbon_buttons.append(carbon_button)
+
+ self.add_edge()
+ return carbon_button
+
+ def add_edge(self):
+ self.graph.setdefault(self.this_node, [])
+ self.graph.setdefault(self.next_node, [])
+
+ self.graph[self.this_node].append(self.next_node)
+ self.graph[self.next_node].append(self.this_node)
+
+ @staticmethod
+ def set_properties(*widgets):
+ for widget in widgets:
+ widget.set_can_focus(False)
+ widget.show()
+
+ # def get_adjacent_atoms(self, button: Gtk.Button, initial_pos: tuple, canvas: Gtk.Container):
+ # if button.get_label() == "C": # Can't place any more atoms when octet is satisfied
+ # return False, None, None
+ #
+ # # assert len(initial_pos) == 2
+ #
+ # current_x, current_y = initial_pos
+ #
+ # adj_positions = tuple(tuple(canvas.child_get(self.carbon_buttons[adj - 1], "x", "y"))
+ # for adj in self.graph[self.this_node]) # Obtain coords of each adj atom
+ #
+ # print(adj_positions)
+ #
+ # if all(adj_x != current_x + 45 for adj_x, adj_y in adj_positions):
+ # return True, current_x + 45, current_y
+ # elif all(adj_x != current_x - 45 for adj_x, adj_y in adj_positions):
+ # return True, current_x - 45, current_y
+ # elif all(adj_y != current_y + 23 for adj_x, adj_y in adj_positions):
+ # return True, current_x, current_y + 23
+ # elif all(adj_y != current_y - 23 for adj_x, adj_y in adj_positions):
+ # return True, current_x, current_y - 23
+ #
+ # return True, 323, 142
+
+ # def do_physics(self):
+ # force_on_node =
\ No newline at end of file
diff --git a/carbonpy/namer.py b/carbonpy/namer.py
new file mode 100644
index 0000000..79e42c7
--- /dev/null
+++ b/carbonpy/namer.py
@@ -0,0 +1,172 @@
+from collections import deque
+from typing import Union, List
+
+from base.compound import CompoundObject
+from base.element import Element
+from constants import multipl_suffixes
+from rules.retained import convert
+from rules.prefixes import get_prefix
+
+
+class BaseNamer(CompoundObject):
+ def analyser(self) -> str:
+ compound_name = ""
+ many_bonds = "" # Is empty for saturated compounds
+
+ # Checks valencies of atoms in compound-
+ self.valency_checker()
+ # Processing and deciding name(s) of the compound-
+ bond_type = self.suffix_namer()
+
+ if any(suffix in bond_type for suffix in list(multipl_suffixes.values())):
+ many_bonds += "a-" # This is the 'a' in a compound like butadiene, for euphonic reasons
+ elif not bond_type == "ane": # If compound has only one unsaturated bond
+ many_bonds += "-"
+ compound_name += f"{get_prefix(count=self.carbons, parent=True)}{many_bonds}{bond_type}"
+
+ if compound_name in convert:
+ compound_name = convert[compound_name]
+
+ return compound_name # returns final name
+
+ def suffix_namer(self) -> str:
+ lowest_db = lowest_tb = db_suffix = tb_suffix = "" # db,tb- double, triple bond
+
+ lows_pos = self.lowest_position()
+ if not isinstance(lows_pos, dict): # If compound is saturated
+ return "ane" # Alkane
+
+ for key, value in lows_pos.items():
+ if value == '=':
+ lowest_db += f"{key}," # Adds position of double bond with ',' for more bonds
+ elif value == '~':
+ lowest_tb += f"{key}," # Same as above, except this time for triple bond
+
+ lowest_tb = lowest_tb.strip(',') # Removes ','
+ lowest_db = lowest_db.strip(',')
+
+ # If many double/triple bonds present, get their suffix(di, tri, tetra, etc.)
+ if len(lowest_db) >= 3:
+ db_suffix = f"-{multipl_suffixes[len(lowest_db.replace(',', ''))]}" # Add that '-' too
+ else:
+ db_suffix += "-" # else only '-'
+
+ if len(lowest_tb) >= 3:
+ tb_suffix = f"-{multipl_suffixes[len(lowest_tb.replace(',', ''))]}"
+ else:
+ tb_suffix += "-"
+
+ if '=' in self.processing and '~' in self.processing: # If double and triple bond present
+ return f"{lowest_db}{db_suffix}en-{lowest_tb}{tb_suffix}yne"
+
+ elif '~' in self.processing: # Only triple bond present
+ return f"{lowest_tb}{tb_suffix}yne"
+
+ elif '=' in self.processing: # Only double bond present
+ return f"{lowest_db}{db_suffix}ene" # Return with di,tri,etc
+
+ def bonds_only(self):
+ self.processing = self.processing.translate({ord(i): None for i in 'CH23'}) # Removes everything except bonds
+
+ def lowest_position(self) -> Union[None, dict]:
+ """First point of difference rule used"""
+ lowest_front = {}
+ lowest_back = {}
+ # TODO: Maybe number from front and back simultaneously? (Also made me realize this may not work for isomers)
+ self.bonds_only()
+ # print(self.processing)
+ # Adds all occurrences from front
+ for index, string in enumerate(self.processing):
+ if string in ('=', '~'):
+ lowest_front[index + 1] = string # Adds position no. of bond
+
+ # Adds all occurrences from back
+ for index, string in enumerate(''.join(reversed(self.processing))):
+ if string in ('=', '~'):
+ lowest_back[index + 1] = string
+
+ assert (len(lowest_front) == len(lowest_back)) # Make sure they have the same length
+ for (index, value), (index2, value2) in zip(lowest_front.items(), lowest_back.items()):
+ # First point of difference-
+ if index < index2:
+ return lowest_front
+ elif index2 < index:
+ return lowest_back
+ elif index == index2: # Same index, check for precedence (only = and ~ for now)
+ # Double bond has more precedence than triple
+ if value == '=': # Will change into a dict access for func groups priority
+ return lowest_front
+ elif value2 == '=':
+ return lowest_back
+
+ if len(lowest_front) == 0:
+ return None
+ else:
+ return lowest_back # Can also return front(if compound is symmetrical)
+
+ def priority_order(self):
+ pass
+
+
+class Branched(BaseNamer):
+ def traverse_node(self, terminal_node: Element):
+ # Using DFS approach-
+ stack, visited = deque([terminal_node.value]), deque([terminal_node.value])
+ path = terminal_node.value
+
+ while stack:
+ # noinspection PyTypeChecker
+ next_nodes = self.graph[stack[-1]]
+ if len(next_nodes) == 1 and path.count('C') > 1: # When terminal node is reached
+ yield path.split('-')
+
+ for node in next_nodes:
+ if node not in visited:
+ path += f"-{node}"
+ visited.append(node)
+ stack.append(node)
+ break
+ else:
+ stack.pop()
+ path = "-".join(stack)
+
+ def determine_longest(self):
+ possible_paths: List[List[str]] = []
+ longest_paths: List[List[str]] = []
+ length: int = 1
+
+ def calculate_longest(path_list):
+ nonlocal length, longest_paths
+ if len(path_list) > length:
+ length = len(path_list)
+ longest_paths = [path_list]
+ elif len(path_list) == length:
+ longest_paths.append(path_list)
+
+ for terminal_node, value in self.graph.items():
+ if len(value) == 1:
+ for path in self.traverse_node(terminal_node=terminal_node):
+ possible_paths.append(path)
+
+ list(map(calculate_longest, possible_paths))
+ # print(possible_paths)
+ # print(f"Number of possible paths: {len(possible_paths)}")
+ # print(longest_paths)
+ return longest_paths
+
+ # def branch_splitter(self): # split branches and pass each of them to longest()
+ #
+ # print(self.processing)
+ # regex = re.compile('\((.*?)\)')
+ # chain = re.compile('C[H]*\((.*?)\).+')
+ # branches: list = regex.findall(self.processing)
+ # chained = chain.search(self.processing)
+ # print(chained)
+ # if branches:
+ # print(f"matched: {branches}")
+ # for match in branches:
+ # self.processing = re.sub(f'\({match}\)', '', self.processing)
+ # branches.append(self.processing)
+ # print(self.processing)
+ # print(branches)
+ # self.longest(branches)
diff --git a/carbonpy/rules/__init__.py b/carbonpy/rules/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/carbonpy/rules/prefixes.py b/carbonpy/rules/prefixes.py
new file mode 100644
index 0000000..785bbdd
--- /dev/null
+++ b/carbonpy/rules/prefixes.py
@@ -0,0 +1,47 @@
+common = {5: "pent", 6: "hex", 7: "hept", 8: "oct", 9: "non", 10: "dec",
+ 11: "undec", 20: "icos", 30: "tricont", 40: "tetracont", 50: "pentacont", 60: "hexacont",
+ 70: "heptacont", 80: "octacont", 90: "nonacont", 100: "hect", 101: "henhect", 200: "dict", 300: "trict",
+ 400: "tetract", 500: "pentact", 600: "hexact", 700: "heptact", 800: "octact", 900: "nonact", 1000: "kili",
+ 1001: "henkili", 2000: "dili", 3000: "trili", 4000: "tetrali", 5000: "pentali", 6000: "hexali",
+ 7000: "heptali", 8000: "octali", 9000: "nonali"}
+
+# For parent carbon chain-
+prefixes = {1: "meth", 2: "eth", 3: "prop", 4: "but", **common}
+# For multiple branches, bonds-
+multi_prefixes = {1: "mono", 2: "di", 3: "tri", 4: "tetra", **{k: f"{v}a" for k, v in common.items()}}
+
+
+def to_place_values(num: int):
+ if num > 99 and num % 100 == 11:
+ yield 11
+ num -= 11
+
+ value = 1
+ while num != 0:
+ last = num % 10
+ yield last * value
+ num //= 10
+ value *= 10
+
+
+def get_prefix(count: int, parent: bool = False):
+ try:
+ return prefixes[count] if parent else multi_prefixes[count]
+ except KeyError:
+ pass
+
+ prefix = ""
+ for num in to_place_values(count):
+ if num == 0:
+ continue
+ elif num == 1:
+ prefix += "hen"
+ elif num == 2:
+ prefix += "do"
+ elif num == 11: # Special case for 11 where undeca will be used
+ prefix += multi_prefixes[num]
+ elif num == 20 and prefix[-1] in "aeiou": # 'i' in 'icosa' is elided if it's after a vowel
+ prefix += multi_prefixes[num][1:]
+ else:
+ prefix += multi_prefixes[num]
+ return prefix[:-1] if parent else prefix
diff --git a/carbonpy/rules/retained.py b/carbonpy/rules/retained.py
new file mode 100644
index 0000000..96fa5a6
--- /dev/null
+++ b/carbonpy/rules/retained.py
@@ -0,0 +1 @@
+convert = {'2-methylpropan-2-yl': '𝘵𝘦𝘳𝘵-butyl', 'eth-1-yne': 'acetylene'}
diff --git a/carbonpy/version.py b/carbonpy/version.py
new file mode 100644
index 0000000..8f17881
--- /dev/null
+++ b/carbonpy/version.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+# Copyright © 2020 Harshil Mehta
+
+# version.py - File containing current version number.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+__version__ = '0.52'
diff --git a/gui.py b/gui.py
deleted file mode 100644
index 390243d..0000000
--- a/gui.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# The bhaiUI
-
-import tkinter
-
-homescreen = tkinter.Tk()
-canvas = tkinter.canvas(homescreen, width
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_compound.py b/tests/test_compound.py
new file mode 100644
index 0000000..d18b1c2
--- /dev/null
+++ b/tests/test_compound.py
@@ -0,0 +1,70 @@
+import pytest
+from collections import deque
+from carbonpy import ValencyError, CompoundObject, Element
+
+comps = ['CH4', 'CH~CH', 'CH~C-C~C-CH=C=C=CH2', 'CH3-C~C-CH3', 'CH2=CH2', 'CH2=CH-CH=CH-CH=CH2', 'CH~C-CH=CH2']
+
+formulas = ['CH₄', 'C₂H₂', 'C₈H₄', 'C₄H₆', 'C₂H₄', 'C₆H₈', 'C₄H₄']
+
+carbs_hyds = (1, 4), (2, 2), (8, 4), (4, 6), (2, 4), (6, 8), (4, 4)
+
+
+class TestCompoundObject:
+
+ @pytest.mark.parametrize(argnames='compound,atom', argvalues=zip(comps, carbs_hyds))
+ def test_attributes(self, compound, atom):
+ comp = CompoundObject(compound)
+ assert comp.carbons == atom[0]
+ assert comp.hydrogens == atom[1]
+ assert len(comp._carbon_comps) - 1 == len(comp._bonds_only)
+
+ @pytest.mark.parametrize(argnames='compound,molecular_form', argvalues=zip(comps, formulas))
+ def test_molecular_formula(self, compound, molecular_form):
+ assert CompoundObject(compound).molecular_formula() == molecular_form
+
+ def test_molar_mass(self):
+ assert round(CompoundObject('CH~CH').molar_mass, 2) == 26.04
+
+ def test_comparisons(self):
+ assert CompoundObject('CH~CH') > CompoundObject('CH4') == CompoundObject('CH4') != CompoundObject('CH3-CH3')
+
+ def test_valency_checker(self):
+ assert CompoundObject('CH3-CH=CH2').valency_checker()
+ assert CompoundObject('CH3-C(-CH=CH-CH3)(-CH2-C~CH)-CH3').valency_checker()
+
+ with pytest.raises(ValencyError):
+ CompoundObject('CH3-C=CH2').valency_checker()
+ CompoundObject('CH~C-CH(-C(-CH3)(-CH2-CH3)-CH3)-C(-CH=CH(-CH2-CH3)-CH3)(-CH3)-CH2-CH3').valency_checker()
+
+ def test_excess_carbons(self):
+ with pytest.raises(ValueError):
+ CompoundObject(f"CH3{'-CH2' * 10000}-CH3")
+
+ def test_atom_counter(self):
+ with pytest.raises(ValueError):
+ CompoundObject('CH4').atom_counter('N')
+
+ def test_graph(self):
+ a = CompoundObject('CH~C-CH(-C(-CH3)(-CH=CH2)-CH3)-C(-CH=C(-CH3)-CH3)(-CH3)-CH3')
+ graph = {'C1': ['C2'],
+ 'C2': ['C1', 'C3'],
+ 'C3': ['C2', 'C4', 'C9'],
+ 'C4': ['C3', 'C5', 'C6', 'C8'],
+ 'C5': ['C4'],
+ 'C6': ['C4', 'C7'],
+ 'C7': ['C6'],
+ 'C8': ['C4'],
+ 'C9': ['C3', 'C10', 'C14', 'C15'],
+ 'C10': ['C9', 'C11'],
+ 'C11': ['C10', 'C12', 'C13'],
+ 'C12': ['C11'],
+ 'C13': ['C11'],
+ 'C14': ['C9'],
+ 'C15': ['C9']}
+
+ for element, nodes in graph.items():
+ assert a.graph[element] == deque(nodes)
+
+ def test_iteration(self):
+ for element in CompoundObject('CH3-C(-CH=CH-CH3)(-CH2-C~CH)-CH3'):
+ assert isinstance(element, Element)
diff --git a/tests/test_element.py b/tests/test_element.py
new file mode 100644
index 0000000..c933db0
--- /dev/null
+++ b/tests/test_element.py
@@ -0,0 +1,27 @@
+import pytest
+
+from carbonpy import Element
+
+
+element_dict = {}
+values = (1, 2, 3)
+
+elements = [Element('C1', 'CH3'), Element('C2', 'CH'), Element('C3', 'C')]
+
+comp_n_value = (('C1', 'CH3', 3), ('C2', 'CH', 1), ('C3', 'C', 0))
+
+
+@pytest.fixture(params=elements)
+def element(request):
+ return request.param
+
+
+@pytest.mark.parametrize(argnames='element,attr', argvalues=zip(elements, comp_n_value))
+def test_element_attributes(element, attr):
+ assert element.value == attr[0] and element.comp == attr[1] and element.hydrogens() == attr[2]
+
+
+@pytest.mark.parametrize(argnames='element,value', argvalues=zip(elements, values))
+def test_dict_access(element, value):
+ element_dict[element] = value
+ assert element_dict[element] == element_dict[element.value] == value
diff --git a/tests/test_namer.py b/tests/test_namer.py
new file mode 100644
index 0000000..b673fab
--- /dev/null
+++ b/tests/test_namer.py
@@ -0,0 +1,14 @@
+import pytest
+
+from carbonpy import BaseNamer
+
+
+names = ['methane', 'acetylene', 'octa-1,2,3-trien-5,7-diyne', 'but-2-yne', 'eth-1-ene', 'hexa-1,3,5-triene',
+ 'but-1-en-3-yne']
+
+comps = ['CH4', 'CH~CH', 'CH~C-C~C-CH=C=C=CH2', 'CH3-C~C-CH3', 'CH2=CH2', 'CH2=CH-CH=CH-CH=CH2', 'CH~C-CH=CH2']
+
+
+@pytest.mark.parametrize(argnames='compound,name', argvalues=zip(comps, names))
+def test_analyser(compound, name):
+ assert BaseNamer(compound).analyser() == name
diff --git a/tests/test_rules/test_prefixes.py b/tests/test_rules/test_prefixes.py
new file mode 100644
index 0000000..6aa12ec
--- /dev/null
+++ b/tests/test_rules/test_prefixes.py
@@ -0,0 +1,18 @@
+import pytest
+
+from carbonpy.rules.prefixes import to_place_values, get_prefix
+
+numbers = [14, 23, 41, 52, 363]
+prefixes = ["tetradec", "tricos", "hentetracont", "dopentacont", "trihexacontatrict"]
+
+
+def test_to_place_values():
+ assert list(to_place_values(100)) == [0, 0, 100]
+ assert list(to_place_values(123)) == [3, 20, 100]
+ assert list(to_place_values(1043)) == [3, 40, 0, 1000]
+
+
+@pytest.mark.parametrize(argnames='number,prefix', argvalues=zip(numbers, prefixes))
+def test_get_prefix(number, prefix):
+ assert get_prefix(number, parent=True) == prefix
+ assert get_prefix(number, parent=False) == f"{prefix}a"