Source code for goodvibes.media

"""Solvent database for media concentration corrections.

Solvent molecular weights [g/mol] and densities [g/mL] at 20 °C, loaded from
solvents.json alongside this module. Each solvent entry defines a canonical name
and one or more lookup aliases (abbreviations, full names).

Public API:
    solvents  -- dict mapping alias (lowercase str) -> (mw, density) tuple
"""

import json
import os


def _load_solvents():
    """
    Load the bundled solvents.json and build a flat mapping from each lowercase alias to its (molecular weight, density) tuple.

    The JSON file is read from the same directory as this module. Each solvent entry's `aliases` list is expanded and normalized to lowercase.

    Returns:
        dict: Mapping where keys are lowercase alias strings and values are `(mw, density)` tuples — `mw` in g/mol and `density` in g/mL.
    """
    json_path = os.path.join(os.path.dirname(__file__), 'solvents.json')
    with open(json_path, 'r') as f:
        data = json.load(f)

    result = {}
    for entry in data['solvents']:
        value = (entry['mw'], entry['density'])
        for alias in entry['aliases']:
            result[alias.lower()] = value
    return result


solvents = _load_solvents()


[docs] def lookup_solvent(name): """Look up a solvent by alias and return its (mw, density). Raises ValueError with a "did you mean ..." hint and a list of common aliases if `name` is unknown. Use this to validate user input (e.g. --media / --freespace) up-front; `compute_media_conc` and other call sites can then assume the alias is valid. """ key = name.lower() if key not in solvents: import difflib suggestions = difflib.get_close_matches(key, solvents.keys(), n=5, cutoff=0.5) common = ['water', 'methanol', 'ethanol', 'acetone', 'dmso', 'dmf', 'thf', 'benzene', 'toluene', 'chloroform', 'dcm', 'acetonitrile'] msg = f"Unknown solvent {name!r}." if suggestions: msg += f" Did you mean: {', '.join(suggestions)}?" msg += (f" Common aliases: {', '.join(common)} " f"({len(solvents)} total in goodvibes/solvents.json).") raise ValueError(msg) return solvents[key]
[docs] def compute_media_conc(media, file): """ Compute the neat-solvent molar concentration when the output file corresponds to the specified solvent. Parameters: media (str): Solvent name as provided (e.g., from --media). file (str): Path to the output file used to infer the solvent name. Returns: float or None: Neat-solvent concentration in mol/L if the file's solvent matches `media`, `None` otherwise. """ from .utils import display_name key = media.lower() if key in solvents and key == display_name(file).lower(): mweight, density = solvents[key] return (density * 1000) / mweight return None