Skip to content
86 changes: 60 additions & 26 deletions pysipfenn/core/pysipfenn.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import csv
import json
from time import perf_counter
from datetime import datetime
from typing import List, Union, Dict
from importlib import resources

Expand Down Expand Up @@ -49,8 +50,14 @@ class Calculator:
autoLoad: Automatically load all available ML models based on the ``models.json`` file. This `will` require
significant memory and time if they are available, so for featurization and other non-model-requiring
tasks, it is recommended to set this to ``False``. Defaults to ``True``.
verbose: Print initialization messages and several other non-critical messages during runtime procedures.
Defaults to True.
verbose: Controls the verbosity of the ``Calculator`` printout messages. By default, it is set to ``True`` and
shows the level of information that shoudl be optimal for most users. Setting it to ``False`` will
make messages concise and only show critical information. To further suppress messages, use the
``printOut`` switch.
printOut: Controls whether the ``Calculator`` should print any messages to the console. By default, it is set
to ``True``. If set to ``False``, no messages will be printed to the console, regardless of the ``verbose``
setting, but they will be retained in the ``self.printOutLog`` attribute of the ``Calculator`` object, allowing
easy access to the messages if needed for, e.g., debugging purposes.

Attributes:
models: Dictionary with all model information based on the ``models.json`` file in the modelsSIPFENN
Expand All @@ -65,38 +72,47 @@ class Calculator:
of predictions for each structure corresponds to the order of networks in the toRun list.
inputFiles: List of all input file names used during the last predictions run. The order of the list
corresponds to the order of atomic structures given to models as input.
verbose: Boolean controlling the verbosity of the ``Calculator`` printout messages set during initialization.
printOut: Boolean controlling whether the ``Calculator`` should print any messages to the console set during
initialization.
printOutLog: String containing all messages logged by the ``Calculator`` object if ``printOut`` was set to ``True``.
"""

def __init__(self,
autoLoad: bool = True,
verbose: bool = True):
verbose: bool = True,
printOut: bool = True
):
"""Initializes the pySIPFENN Calculator object."""
if verbose:
print('\n********* Initializing pySIPFENN Calculator **********')
self.verbose = verbose
self.printOut = printOut
self.printOutLog = ""

if self.verbose:
self.log('\n********* Initializing pySIPFENN Calculator **********')

# dictionary with all model information
with resources.files('pysipfenn.modelsSIPFENN').joinpath('models.json').open('r') as f:
if verbose:
print(f'Loading model definitions from: {Fore.BLUE}{f.name}{Style.RESET_ALL}')
self.log(f'Loading model definitions from: {Fore.BLUE}{f.name}{Style.RESET_ALL}')
self.models = json.load(f)
# networks list
self.network_list = list(self.models.keys())
if verbose:
print(f'Found {Fore.BLUE}{len(self.network_list)} network definitions in models.json{Style.RESET_ALL}')
if self.verbose:
self.log(f'Found {Fore.BLUE}{len(self.network_list)} network definitions in models.json{Style.RESET_ALL}')
# network names
self.network_list_names = [self.models[net]['name'] for net in self.network_list]
self.network_list_available = []
self.updateModelAvailability()

self.loadedModels = {}
if autoLoad:
print(f'Loading all available models ({Fore.BLUE}autoLoad=True{Style.RESET_ALL})')
self.log(f'Loading all available models ({Fore.BLUE}autoLoad=True{Style.RESET_ALL})')
self.loadModels()
else:
print(f'Skipping model loading ({Fore.BLUE}autoLoad=False{Style.RESET_ALL})')
self.log(f'Skipping model loading ({Fore.BLUE}autoLoad=False{Style.RESET_ALL})')

self.prototypeLibrary = {}
self.parsePrototypeLibrary(verbose=verbose)
self.parsePrototypeLibrary()

self.toRun = []
self.descriptorData = []
Expand All @@ -105,8 +121,8 @@ def __init__(self,
'RSS': []
}
self.inputFiles = []
if verbose:
print(f'{Fore.GREEN}********** Successfully Initialized **********{Style.RESET_ALL}')
if self.verbose:
self.log(f'{Fore.GREEN}********** Successfully Initialized **********{Style.RESET_ALL}')

def __str__(self):
"""Prints the status of the ``Calculator`` object."""
Expand All @@ -127,10 +143,27 @@ def __str__(self):
printOut += f' {len(self.predictions[0])} predictions/structure\n'
return printOut

def log(self, message: str) -> None:
"""Logs a message to the ``self.printOut`` attribute of the ``Calculator`` object if ``self.printOutSet`` is ``False``.
Otherwise, the message is printed to the console as usual. The messages stored in the ``self.printOut`` attribute are
additonally automatically time stamped (YY-MM-DD HH:MM:SS | ) for easy tracking of the events.

Args:
message: Message to log.

Returns:
None
"""
if self.printOut:
print(message, flush=True)
else:
time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.printOutLog += f'{time} | {message}\n'


# ********************************* PROTOTYPE HANDLING *********************************
def parsePrototypeLibrary(self,
customPath: str = "default",
verbose: bool = False,
printCustomLibrary: bool = False) -> None:
"""Parses the prototype library YAML file in the ``misc`` directory, interprets them into pymatgen ``Structure``
objects, and stores them in the ``self.prototypeLibrary`` dict attribute of the ``Calculator`` object. You can use it
Expand All @@ -140,8 +173,6 @@ def parsePrototypeLibrary(self,
Args:
customPath: Path to the prototype library YAML file. Defaults to the magic string ``"default"``, which loads the
default prototype library included in the package in the ``misc`` directory.
verbose: If True, it prints the number of prototypes loaded. Defaults to ``False``, but note that ``Calculator``
class automatically initializes with ``verbose=True``.
printCustomLibrary: If True, it prints the name and POSCAR of each prototype being added to the prototype
library. Has no effect if ``customPath`` is ``'default'``. Defaults to ``False``.

Expand Down Expand Up @@ -175,12 +206,15 @@ class automatically initializes with ``verbose=True``.
'origin': prototype['origin']
}
})
if verbose:
protoLen = len(self.prototypeLibrary)
if protoLen == 0:
print(f"{Style.DIM}No prototypes were loaded into the prototype library.{Style.RESET_ALL}")

protoLen = len(self.prototypeLibrary)
if protoLen == 0:
self.log(f"{Style.DIM}No prototypes were loaded into the prototype library.{Style.RESET_ALL}")
else:
if self.verbose:
self.log(f"Loaded {Fore.GREEN}{protoLen} prototypes {Style.RESET_ALL}into the library: {Fore.BLUE}{', '.join(natsort.natsorted(self.prototypeLibrary.keys()))}{Style.RESET_ALL}")
else:
print(f"Loaded {Fore.GREEN}{protoLen} prototypes {Style.RESET_ALL}into the library.")
self.log(f"Loaded {Fore.GREEN}{protoLen} prototypes {Style.RESET_ALL}into the library.")


def appendPrototypeLibrary(self, customPath: str) -> None:
Expand Down Expand Up @@ -212,16 +246,16 @@ def updateModelAvailability(self) -> None:
if all_files.__contains__(net + '.onnx'):
detectedNets.append(net)
try:
print(f"{Fore.GREEN}✔ {netName}{Style.RESET_ALL}")
self.log(f"{Fore.GREEN}✔ {net:<45} | {netName}{Style.RESET_ALL}")
except UnicodeEncodeError:
# Fallback to ASCII characters if Unicode encoding fails
print(f"{Fore.GREEN}+ {netName}{Style.RESET_ALL}")
self.log(f"{Fore.GREEN}+ {net:<45} | {netName}{Style.RESET_ALL}")
else:
try:
print(f"{Style.DIM}✘ {netName}{Style.RESET_ALL}")
self.log(f"{Style.DIM}✘ {net:<45} | {netName}{Style.RESET_ALL}")
except UnicodeEncodeError:
# Fallback to ASCII characters if Unicode encoding fails
print(f"{Fore.DIM}x {netName}{Style.RESET_ALL}")
self.log(f"{Fore.DIM}x {net:<45} | {netName}{Style.RESET_ALL}")
self.network_list_available = detectedNets

def downloadModels(self, network: str = 'all') -> None:
Expand Down
2 changes: 1 addition & 1 deletion pysipfenn/descriptorDefinitions/KS2022_randomSolutions.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ def profile(test: str = 'FCC',
the descriptor and a dictionary containing the convergence history, or None. In either case, the descriptor
will be persisted in `f'TestResult_KS2022_randomSolution_{test}_{nIterations}iter.csv'` file.
"""
c = pysipfenn.Calculator(autoLoad=False)
c = pysipfenn.Calculator(autoLoad=False, printOut=False)

try:
s = c.prototypeLibrary[test]['structure']
Expand Down
2 changes: 1 addition & 1 deletion pysipfenn/tests/test_Core_prototypeLibrary.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_customPrototypeLoad(self):
library and stay there."""

with resources.files('pysipfenn').joinpath('tests/testCaseFiles/prototypeLibrary-custom.yaml') as f:
self.c.parsePrototypeLibrary(customPath=f, verbose=True, printCustomLibrary=True)
self.c.parsePrototypeLibrary(customPath=f, printCustomLibrary=True)

with self.subTest(msg="Custom prototype present with correct parse"):
self.assertTrue("NicePhase" in self.c.prototypeLibrary)
Expand Down
Loading