Skip to content

Commit

Permalink
Update periodic.py
Browse files Browse the repository at this point in the history
  • Loading branch information
SophiaLi20 authored Feb 28, 2025
1 parent c488bf4 commit 66631c7
Showing 1 changed file with 83 additions and 143 deletions.
226 changes: 83 additions & 143 deletions atomdb/periodic.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from csv import reader

from importlib_resources import files

from atomdb.utils import CONVERTOR_TYPES
from importlib_resources import files # Importing files for accessing package resources
from atomdb.utils import CONVERTOR_TYPES # Importing conversion types from AtomDB utilities

__all__ = [
"Element",
Expand All @@ -13,190 +10,145 @@


def setup_element():
r"""Generate the ``Element`` class and helper functions."""
"""Generate the `Element` class and helper functions."""

# Extract element data and metadata
data, props, srcs, units, prop2col, num2str, str2num = get_data()
prop2name, prop2desc, prop2src, prop2url, prop2note = get_info()

# Function to retrieve atomic number from element symbol/name
def element_number(elem):
(
"Return the element number from a string or int.\n"
"\n"
"Parameters\n"
"----------\n"
"elem: (str | int)\n"
" Symbol, name, or number of an element.\n"
"\n"
"Returns\n"
"-------\n"
"atnum : int\n"
" Atomic number.\n"
)
"""
Return the element number from a string or integer.
Parameters:
elem (str | int): Symbol, name, or number of an element.
Returns:
int: Atomic number of the element.
"""
if isinstance(elem, str):
return str2num[elem]
return str2num[elem] # Convert symbol or name to atomic number
else:
atnum = int(elem)
if atnum not in num2str:
raise ValueError(f"Invalid element number: {atnum}")
return atnum

# Function to get the symbol of an element
def element_symbol(elem):
(
"Return the element symbol from a string or int.\n"
"\n"
"Parameters\n"
"----------\n"
"elem: (str | int)\n"
" Symbol, name, or number of an element.\n"
"\n"
"Returns\n"
"-------\n"
"symbol : str\n"
" Element symbol.\n"
)
return num2str[element_number(elem) if isinstance(elem, str) else int(elem)][0]
"""
Return the element symbol from a string or integer.
Parameters:
elem (str | int): Symbol, name, or number of an element.
Returns:
str: Element symbol.
"""
return num2str[element_number(elem)][0] # Convert element to symbol

# Function to get the full name of an element
def element_name(elem):
(
"Return the element name from a string or int.\n"
"\n"
"Parameters\n"
"----------\n"
"elem: (str | int)\n"
" Symbol, name, or number of an element.\n"
"\n"
"Returns\n"
"-------\n"
"name : str\n"
" Element name.\n"
)
return num2str[element_number(elem) if isinstance(elem, str) else int(elem)][1]
"""
Return the element name from a string or integer.
Parameters:
elem (str | int): Symbol, name, or number of an element.
Returns:
str: Element name.
"""
return num2str[element_number(elem)][1] # Convert element to name

# Class constructor for `Element`
def init(self, elem):
(
"Initialize an ``Element`` instance.\n"
"\n"
"Parameters\n"
"----------\n"
"elem : (str | int)\n"
" Symbol, name, or number of an element.\n"
)
self._atnum = element_number(elem)
"""
Initialize an `Element` instance.
Parameters:
elem (str | int): Symbol, name, or number of an element.
"""
self._atnum = element_number(elem) # Store atomic number as an instance variable

# Define properties for Element class
@property
def atnum(self):
"""Returns atomic number of the element."""
return self._atnum

@property
def atsym(self):
return element_symbol(self._atnum)

# atnum.__doc__ = "Atomic number of the element.\n" "\n" "Returns\n" "-------\n" "atnum : int\n"

@property
def symbol(self):
"""Returns symbol of the element."""
return element_symbol(self._atnum)

symbol.__doc__ = "Symbol of the element.\n" "\n" "Returns\n" "-------\n" "symbol : str\n"

@property
def name(self):
"""Returns name of the element."""
return element_name(self._atnum)

name.__doc__ = "Name of the element.\n" "\n" "Returns\n" "-------\n" "name : str\n"

# Element attributes; add __init__ method
# Define attributes for the dynamically created `Element` class
attrs = {
"__init__": init,
"atnum": atnum,
"symbol": symbol,
"name": name,
}

# ELement class docstring header
class_doc = (
"Element properties.\n"
"\n"
"Attributes\n"
"----------\n"
"atnum : int\n"
" Atomic number of the element.\n"
"symbol : str\n"
" Symbol of the element.\n"
"name : str\n"
" Name of the element.\n"
)

# Autocomplete class docstring with data from the CSV files
# Generate properties dynamically based on CSV data
for prop, name in prop2name.items():
# Add signature, description, sources, units, urls, and notes
short = f"{name} of the element."

# Handle cases where there are multiple data sources
if len(prop2col[prop]) == 1 and "" in prop2col[prop]:
# Only one default source
t = type(data[0][prop2col[prop][""]]).__name__
sig = f"{prop} : {t}"
long = ""
else:
# Multiple or non-default sources
t = type(data[0][next(iter(prop2col[prop].values()))]).__name__
sig = f"{prop} : Dict[{t}]"
long = "\n" "Notes\n" "-----\n" "This property is a dictionary with the following keys:"
# Add unit, url, note for each source
for src in prop2col[prop].keys():
long += f'\n * "{src}"'
if prop2src[prop][src] != "":
long += "\n * Source\n" f"{indent_lines(prop2src[prop][src], 12)}"
if units[prop2col[prop][src]] != "":
long += "\n * Units\n" f"{indent_lines(units[prop2col[prop][src]], 12)}"
if prop2url[prop][src] != "":
long += "\n * URL\n" f"{indent_lines(prop2url[prop][src], 12)}"
if prop2note[prop][src] != "":
long += "\n * Notes\n" f"{indent_lines(prop2note[prop][src], 12)}"
long += "\n"

# Add property to class docstring
# class_doc += f"{sig}\n" f" {short}\n"

# Make property method for Element class with docstring
long = "\nNotes\n-----\nThis property is a dictionary with source keys."

# Generate class property dynamically
f = make_property(data, prop, prop2col)
f.__doc__ = f"{short}\n" "\n" "Returns\n" "-------\n" f"{sig}\n" f"{long}"
f.__doc__ = f"{short}\n\nReturns\n-------\n{sig}\n{long}"

# Add property method to attributes
attrs[prop] = f
attrs[prop] = f # Add property to attributes dictionary

# Add class docstring to attributes
attrs["__doc__"] = class_doc
attrs["__doc__"] = "Element properties with attributes like atomic number, symbol, and name."

# Construct Element class
# Dynamically create `Element` class
Element = type("Element", (object,), attrs)

# Return constructed class and functions
return Element, element_number, element_symbol, element_name


def read_csv(file):
r"""Read a CSV file into a list of lists."""
"""Read a CSV file into a list of lists while ignoring empty lines and comments."""
lines = []
with open(file) as f:
for row in reader(f):
# ignore comments and empty lines
# Ignore empty lines and comment lines
if row and not row[0].lstrip().startswith("#") and any(c not in ", \n" for c in row):
# Replace \n with new line in each element
lines.append([i.replace("\\n", "\n") for i in row])
lines.append([i.replace("\\n", "\n") for i in row]) # Replace "\n" escape sequences
return lines


def get_data():
r"""Extract the contents of ``data/elements_data.csv``."""
"""Extract the contents of `data/elements_data.csv`."""
data = read_csv(files("atomdb.data").joinpath("elements_data.csv"))
# Get property keys/source keys/units
props = data.pop(0)[3:]

# Extract property headers and metadata
props = data.pop(0)[3:] # Skip first 3 columns (atomic number, symbol, name)
srcs = data.pop(0)[3:]
units = data.pop(0)[3:]
# Create utility dictionary to locate the columns of the properties

# Create a mapping from property names to their corresponding column indices
prop2col = {}
for col, (prop, key) in enumerate(zip(props, srcs)):
prop2col.setdefault(prop, {})[key] = col
# Create maps from symbol/name to element number

# Maps for converting symbols and names to atomic numbers
num2str, str2num = {}, {}
for row in data:
atnum = int(row.pop(0))
Expand All @@ -205,54 +157,42 @@ def get_data():
num2str[atnum] = (symbol, name)
str2num[symbol] = atnum
str2num[name] = atnum
# add support for all lowercase names
str2num[name.lower()] = atnum
# Convert the rest of the data to numbers
str2num[name.lower()] = atnum # Support lowercase names

# Convert string data to numerical values where applicable
for i, (unit, val) in enumerate(zip(units, row)):
row[i] = CONVERTOR_TYPES[unit](val) if val else None

return data, props, srcs, units, prop2col, num2str, str2num


def get_info():
r"""Extract the contents of ``data/data_info.csv``."""
"""Extract metadata from `data/data_info.csv`."""
info = read_csv(files("atomdb.data").joinpath("data_info.csv"))
prop2name = {}
prop2desc = {}
prop2src = {}
prop2url = {}
prop2note = {}

# Dictionaries to store additional information
prop2name, prop2desc, prop2src, prop2url, prop2note = {}, {}, {}, {}, {}
for prop, name, key, desc, src, url, note in info:
prop2name[prop] = name
prop2desc.setdefault(prop, {})[key] = desc
prop2src.setdefault(prop, {})[key] = src
prop2url.setdefault(prop, {})[key] = url
prop2note.setdefault(prop, {})[key] = note
return prop2name, prop2desc, prop2src, prop2url, prop2note


def indent_lines(input_string, indent):
r"""Indent each line of a string by a given number of spaces."""
lines = input_string.splitlines()
indented_lines = [(indent * " ") + line for line in lines]
return "\n".join(indented_lines)
return prop2name, prop2desc, prop2src, prop2url, prop2note


def make_property(data, prop, prop2col):
r"""Construct a property method for the Element class."""

"""Dynamically generate properties for the `Element` class."""
if len(prop2col[prop]) == 1 and "" in prop2col[prop]:

def f(self):
return data[self._atnum - 1][prop2col[prop][""]]

else:

def f(self):
row = data[self._atnum - 1]
return {k: row[v] for k, v in prop2col[prop].items() if row[v] is not None}

return property(f)


# Generate class and functions to export
# Generate the `Element` class and helper functions
Element, element_number, element_symbol, element_name = setup_element()

0 comments on commit 66631c7

Please sign in to comment.