Skip to content

Commit

Permalink
Merge pull request #31 from bjmorgan/jsonable
Browse files Browse the repository at this point in the history
Jsonable

closes #21 and #28
  • Loading branch information
alexsquires authored May 16, 2023
2 parents d3dc69e + 2240acf commit 16b108a
Show file tree
Hide file tree
Showing 17 changed files with 694 additions and 339 deletions.
3 changes: 3 additions & 0 deletions docs/source/cli_tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ energies. In this case defect system is defined by a ``.yaml`` file structured l
nsites: 1 # site degeneracy of the defect species
charge_states:
0: # charge of first charge state
charge: 0
formation_energy: 2 # formation energy of first charge state
degeneracy: 1 # degeneracy of first charge state
-1:
charge: -1
formation_energy: 1
degeneracy: 2
... # repeat for each defect in your system
Expand Down Expand Up @@ -74,6 +76,7 @@ and specify the concentration of a defect charge state like so::
nsites: 1
charge_states:
-1:
charge: -1
formation_energy: 1
fixed_concentration: 1e20
degeneracy: 1
Expand Down
111 changes: 57 additions & 54 deletions docs/source/tutorial.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion py_sc_fermi/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.1.0"
__version__ = "2.0.0"
2 changes: 1 addition & 1 deletion py_sc_fermi/cli/sc_fermi_solve.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def main():
defect_system = DefectSystem.from_input_set(input_data)
defect_system.report()

dump_dict = defect_system.as_dict(decomposed=True)
dump_dict = defect_system.concentration_dict(decomposed=True)
dump_dict["temperature"] = defect_system.temperature
with open("py_sc_fermi_out.yaml", "w") as f:
yaml.dump(dump_dict, f)
Expand Down
53 changes: 50 additions & 3 deletions py_sc_fermi/defect_charge_state.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import numpy as np # type: ignore
from scipy.constants import physical_constants # type: ignore
from typing import Optional
import warnings

kboltz = physical_constants["Boltzmann constant in eV/K"][0]

Expand All @@ -22,7 +23,7 @@ def __init__(
energy: Optional[float] = None,
fixed_concentration: Optional[float] = None,
):
if energy == None and fixed_concentration == None:
if energy is None and fixed_concentration is None:
raise ValueError(
"""You must specify either a fixed concentration or energy for
this defect! \n Note, if you specify both, the concentration
Expand Down Expand Up @@ -113,7 +114,6 @@ def from_string(
Returns:
``DefectChargeState``: relevant ``DefectChargeState`` object
"""
string = string.strip()
stripped_string = string.split()
if frozen is False:
return cls(
Expand All @@ -132,6 +132,53 @@ def from_string(
fixed_concentration=float(stripped_string[2]) / 1e24 * volume,
)

@classmethod
def from_dict(cls, dictionary: dict) -> "DefectChargeState":
"""generate a dictionary from a ``DefectChargeState`` object
Args:
dictionary (dict): dictionary defining ``DefectChargeState``. Any
fixed concentration given should be provided per-unit cell
Returns:
DefectChargeState: object described by `dictionary`
"""

valid_keys = ["degeneracy", "energy", "charge", "fixed_concentration"]
unrecognized_keys = set(dictionary.keys()) - set(valid_keys)
if unrecognized_keys:
warnings.warn(f"Ignoring unrecognized keys: {', '.join(unrecognized_keys)}")

if "fixed_concentration" in dictionary.keys():
return DefectChargeState(
degeneracy=dictionary["degeneracy"],
charge=dictionary["charge"],
fixed_concentration=dictionary["fixed_concentration"],
)
else:
return DefectChargeState(
degeneracy=dictionary["degeneracy"],
energy=dictionary["energy"],
charge=dictionary["charge"],
)

def as_dict(self) -> dict:
"""generate a dictionary representation of the ``DefectChargeState``
Returns:
dict: dictionary representation of the ``DefectChargeState``
"""

defect_dict = {
"degeneracy": int(self.degeneracy),
"energy": self.energy,
"charge": int(self.charge),
}
if self.fixed_concentration != None:
defect_dict.update({"fixed_concentration": self.fixed_concentration})

return defect_dict

def fix_concentration(self, concentration: float) -> None:
"""Fixes the concentration (per unit cell) of this ``DefectChargeState``
Expand Down Expand Up @@ -172,7 +219,7 @@ def get_concentration(self, e_fermi: float, temperature: float) -> float:
Returns:
float: Concentration at the specified Fermi energy and temperature.
"""
if self.fixed_concentration == None:
if self.fixed_concentration is None:
expfac = -self.get_formation_energy(e_fermi) / (kboltz * temperature)
concentration = self.degeneracy * np.exp(expfac)
else:
Expand Down
101 changes: 52 additions & 49 deletions py_sc_fermi/defect_species.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,21 @@ def nsites(self) -> int:
return self._nsites

@property
def charge_states(self,) -> Dict[int, DefectChargeState]:
def charge_states(
self,
) -> Dict[int, DefectChargeState]:
"""
Returns:
Dict[int, DefectChargeState]: The charge states of this defect species as s dictionary of
``{charge (int): DefectChargeState}`` key-value pairs"""
Dict[int, DefectChargeState]: The charge states of this defect
species as a dictionary of ``{charge (int): DefectChargeState}``
key-value pairs"""
return self._charge_states

@property
def charges(self) -> List[int]:
"""list of all the charges of the ``DefectChargeState`` objects that comprise
this ``DefectSpecies``
"""list of all the charges of the ``DefectChargeState`` objects that
comprise this ``DefectSpecies``
Returns:
List[int]: list of charge states of this ``DefectSpecies``
Expand All @@ -82,30 +85,29 @@ def fixed_concentration(self) -> Optional[float]:
concentration of this defect is variable.
Returns:
Optional[float]: fixed concentration per unit cell of the ``DefectSpecies``
Optional[float]: fixed concentration per unit cell of the
``DefectSpecies``
"""
return self._fixed_concentration

def __repr__(self):
to_return = f"\n{self.name}, nsites={self.nsites}"
if self.fixed_concentration != None:
if self.fixed_concentration is not None:
to_return += f"\nfixed [c] = {self.fixed_concentration}"
to_return += "\n" + "".join(
[f" {cs.__repr__()}\n" for cs in self.charge_states.values()]
)
return to_return

@classmethod
def from_dict(cls, defect_species_dict: dict, volume: Optional[float] = None):
def from_dict(cls, defect_species_dict: dict):
"""return a ``DefectSpecies`` object from a dictionary containing the defect
species data. Primarily for use defining a full ``DefectSystem`` from a
.yaml file.
Args:
defect_species_dict (dict): dictionary containing the defect species
data.
volume (Optional[float], optional): volume of the unit cell.
Defaults to ``None``.
Raises:
ValueError: if any of the ``DefectChargeState`` objects specified have no
Expand All @@ -114,47 +116,25 @@ def from_dict(cls, defect_species_dict: dict, volume: Optional[float] = None):
Returns:
DefectChargeState: as specified by the provided dictionary
"""
charge_states = []
name = list(defect_species_dict.keys())[0]
for n, c in defect_species_dict[name]["charge_states"].items():
if "fixed_concentration" not in list(c.keys()):
fixed_concentration = None
elif volume is not None:
fixed_concentration = float(c["fixed_concentration"]) / 1e24 * volume
if "formation_energy" not in list(c.keys()):
formation_energy = None
else:
formation_energy = float(c["formation_energy"])
if formation_energy == None and fixed_concentration == None:
raise ValueError(
f"{name, n} must have one or both fixed concentration or formation energy"
)
charge_state = DefectChargeState(
charge=n,
energy=formation_energy,
degeneracy=c["degeneracy"],
fixed_concentration=fixed_concentration,
)
charge_states.append(charge_state)

if (
"fixed_concentration" in defect_species_dict[name].keys()
and volume is not None
):
fixed_concentration = (
float(defect_species_dict[name]["fixed_concentration"]) / 1e24 * volume
)
defect_charge_list = [
DefectChargeState.from_dict(charge_state_dictionary)
for charge_state_dictionary in defect_species_dict["charge_states"].values()
]
defect_charge_states = {
charge_state.charge: charge_state for charge_state in defect_charge_list
}
if "fixed_concentration" not in defect_species_dict.keys():
return cls(
name,
defect_species_dict[name]["nsites"],
charge_states={cs.charge: cs for cs in charge_states},
fixed_concentration=fixed_concentration,
name=defect_species_dict["name"],
nsites=defect_species_dict["nsites"],
charge_states=defect_charge_states,
)
else:
return cls(
name,
defect_species_dict[name]["nsites"],
charge_states={cs.charge: cs for cs in charge_states},
name=defect_species_dict["name"],
nsites=defect_species_dict["nsites"],
charge_states=defect_charge_states,
fixed_concentration=defect_species_dict["fixed_concentration"],
)

@classmethod
Expand Down Expand Up @@ -205,6 +185,26 @@ def charge_states_by_formation_energy(
key=lambda x: x.get_formation_energy(e_fermi),
)

def as_dict(self) -> dict:
"""get representation of ``DefectSpecies`` as a dictionary
Returns:
dict: dictionary representation of ``DefectChargeState``
"""

charge_state_dicts = {
int(k): v.as_dict() for k, v in self.charge_states.items()
}
defect_dict = {
"name": str(self.name),
"nsites": int(self.nsites),
"charge_states": charge_state_dicts,
}
if self.fixed_concentration is not None:
defect_dict.update({"fixed_concentration": float(self.fixed_concentration)})

return defect_dict

def min_energy_charge_state(self, e_fermi: float) -> DefectChargeState:
"""Returns the defect charge state with the minimum energy at a given
Fermi energy.
Expand Down Expand Up @@ -311,9 +311,12 @@ def get_concentration(self, e_fermi: float, temperature: float) -> float:
else:
return sum(self.charge_state_concentrations(e_fermi, temperature).values())

def fixed_conc_charge_states(self,) -> Dict[int, DefectChargeState]:
def fixed_conc_charge_states(
self,
) -> Dict[int, DefectChargeState]:
"""get ``DefectChargeState`` objects of this ``DefectSpecies`` with fixed
concentration (i.e those for which ``DefectChargeState.fixed_concentration != None``)
concentration
(i.e those for which ``DefectChargeState.fixed_concentration != None``)
Returns:
Dict[int, DefectChargeState]: key-value pairs of charge on fixed
Expand Down
Loading

0 comments on commit 16b108a

Please sign in to comment.