.get_missing_properties()` to check all missing properties"
]
}
],
"source": [
"# For example, we will receive an error if we try to compile cmps1,\n",
"# because there is not enough data\n",
"cmps1.compile()"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "651669ed",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['S_excess',\n",
" 'H_excess',\n",
" 'mu',\n",
" 'kappa',\n",
" 'V',\n",
" 'S',\n",
" 'H',\n",
" 'Cn',\n",
" 'Psat',\n",
" 'Hvap',\n",
" 'sigma',\n",
" 'epsilon',\n",
" 'Dortmund',\n",
" 'UNIFAC',\n",
" 'PSRK',\n",
" 'Hf',\n",
" 'LHV',\n",
" 'HHV',\n",
" 'combustion',\n",
" 'Tt',\n",
" 'Hfus',\n",
" 'Tb',\n",
" 'Pc',\n",
" 'Pt',\n",
" 'Vc',\n",
" 'Sfus',\n",
" 'Tm',\n",
" 'Tc',\n",
" 'omega',\n",
" 'iscyclic_aliphatic',\n",
" 'dipole',\n",
" 'similarity_variable']"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Before compiling, you can see exactly what each component still lacks with\n",
"# `get_missing_properties` (here XPAO has no thermodynamic models yet)\n",
"XPAO.get_missing_properties()"
]
},
{
"cell_type": "markdown",
"id": "4ec1fad9",
"metadata": {},
"source": [
"\n",
"\n",
"**Note:** Some of these components (e.g., `XPAO`, measured as COD) are defined with a `measured_as` basis, so their molecular weight is not consistent with that basis and `qsdsan` cannot compute accurate molar/volumetric flows for them. `compile()` raises a `RuntimeError` to flag this. Pass `ignore_inaccurate_molar_weight=True` to proceed using mass-based quantities only, or `adjust_MW_to_measured_as=True` to fix the MW for components that have a chemical formula.\n",
"\n",
"
"
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "553a8e0c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CompiledComponents([\n",
" XPAO, SNH4, H2O,\n",
" Methanol, Ethanol,\n",
"])\n"
]
}
],
"source": [
"# We can either provide the missing data as indicated in the error method,\n",
"# or we can use the `default` method and copy some data from H2O (or other more relevant components)\n",
"for i in cmps1:\n",
" if i is H2O:\n",
" continue # \"continue\" means skip the rest of the codes and continue with the next one in the loop\n",
" i.default()\n",
" i.copy_models_from(H2O, names=('sigma', 'epsilon', 'kappa', 'V', 'Cn', 'mu'))\n",
" \n",
"# Now we can compile\n",
"cmps1.compile(ignore_inaccurate_molar_weight=True)\n",
"cmps1.show()"
]
},
{
"cell_type": "markdown",
"id": "bddf6669",
"metadata": {},
"source": [
"\n",
"\n",
"**Shortcut:** Instead of filling in models component by component, `Components.default_compile()` auto-fills the missing properties (boiling point and molar volume from a phase-appropriate reference, the rest from water) and compiles in one call. This is what `load_default()` uses under the hood.\n",
"\n",
"
"
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "1f2a6cb8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CompiledComponents([\n",
" S_H2, S_CH4, S_CH3OH, S_Ac, \n",
" S_Prop, S_F, S_U_Inf, S_U_E, \n",
" C_B_Subst, C_B_BAP, C_B_UAP, C_U_Inf, \n",
" X_B_Subst, X_OHO_PHA, X_GAO_PHA, X_PAO_PHA,\n",
" X_GAO_Gly, X_PAO_Gly, X_OHO, X_AOO, \n",
" X_NOO, X_AMO, X_PAO, X_MEOLO, \n",
" X_FO, X_ACO, X_HMO, X_PRO, \n",
" X_U_Inf, X_U_OHO_E, X_U_PAO_E, X_Ig_ISS, \n",
" X_MgCO3, X_CaCO3, X_MAP, X_HAP, \n",
" X_HDP, X_FePO4, X_AlPO4, X_AlOH, \n",
" X_FeOH, X_PAO_PP_Lo, X_PAO_PP_Hi, S_NH4, \n",
" S_NO2, S_NO3, S_PO4, S_K, \n",
" S_Ca, S_Mg, S_CO3, S_N2, \n",
" S_O2, S_CAT, S_AN, H2O, \n",
"])\n"
]
}
],
"source": [
"# For the default `Components` objects, the easy way is to let it compile during loading\n",
"cmps3 = qs.Components.load_default()\n",
"cmps3.show()"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "11e9507d",
"metadata": {
"tags": [
"raises-exception"
]
},
"outputs": [
{
"ename": "TypeError",
"evalue": "'CompiledComponents' object is read-only",
"output_type": "error",
"traceback": [
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
"\u001b[31mTypeError\u001b[39m Traceback (most recent call last)",
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[31]\u001b[39m\u001b[32m, line 3\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;66;03m# Once compiled, you can no longer add `Component` to it\u001b[39;00m\n\u001b[32m 2\u001b[39m \u001b[38;5;66;03m# so the following code will raise an error\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m3\u001b[39m cmps1.append(cmps3.S_H2)\n",
"\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\Yalin\\Documents\\Coding\\QSDsan-platform\\.venv\\Lib\\site-packages\\thermosteam\\utils\\decorators\\read_only.py:14\u001b[39m, in \u001b[36mdeny\u001b[39m\u001b[34m(self, *args, **kwargs)\u001b[39m\n\u001b[32m 13\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mdeny\u001b[39m(\u001b[38;5;28mself\u001b[39m, *args, **kwargs):\n\u001b[32m---> \u001b[39m\u001b[32m14\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mtype\u001b[39m(\u001b[38;5;28mself\u001b[39m).\u001b[34m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m\u001b[33m object is read-only\u001b[39m\u001b[33m\"\u001b[39m)\n",
"\u001b[31mTypeError\u001b[39m: 'CompiledComponents' object is read-only"
]
}
],
"source": [
"# Once compiled, you can no longer add `Component` to it\n",
"# so the following code will raise an error\n",
"cmps1.append(cmps3.S_H2)"
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "03989f52",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Components([\n",
" XPAO, SNH4, H2O, Methanol,\n",
" Ethanol, S_H2,\n",
"])\n"
]
}
],
"source": [
"# If you really need to add something, the best way (other than adding it before compiling)\n",
"# would be to make a new `Components` containing all the `Component` objects in the `CompiledComponent`\n",
"cmps4 = qs.Components(cmps1)\n",
"cmps4.append(cmps3.S_H2)\n",
"cmps4.show()"
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "471a04ad",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CompiledComponents([S_CH4, S_Ac, S_F, S_NH4])\n"
]
}
],
"source": [
"# If you only want to work with a subset of the `CompiledComponents`, \n",
"# you can hand pick the `Component` IDs you want and input it to the \n",
"# `.subgroup()` method.\n",
"cmps5 = cmps3.subgroup(['S_CH4', 'S_Ac', 'S_F', 'S_NH4'])\n",
"cmps5.show()"
]
},
{
"cell_type": "markdown",
"id": "5139e289",
"metadata": {},
"source": [
"\n",
"\n",
"**Tip:** Why do we go through all the trouble to do the compilation? This is because compilation allows us to \"freezes\" the order of the properties (e.g., MW) and work with numbers only. It also allows us to use libraries like `numpy` and `numba` to speed things up. To learn more, check out this [paper](https://www.nature.com/articles/s41586-020-2649-2) on scientific application of `numpy`.\n",
"\n",
"
"
]
},
{
"cell_type": "code",
"execution_count": 34,
"id": "c195c059",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"H2O\n",
"H2O\n"
]
}
],
"source": [
"# Once compiled, you can set synonyms of `Component` so that you can find one component by any of its synonyms\n",
"# note that its ID won't change\n",
"cmps1.set_synonym('H2O', 'Water')\n",
"print(cmps1.H2O.ID)\n",
"print(cmps1.Water.ID)"
]
},
{
"cell_type": "code",
"execution_count": 35,
"id": "f919549b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# In fact, all of the synonyms point to the same object\n",
"cmps1.H2O is cmps1.Water"
]
},
{
"cell_type": "markdown",
"id": "10207380",
"metadata": {},
"source": [
"\n",
"Python Aside: identity vs. equality (click to expand)
\n",
"\n",
"`is` checks whether two names point to the **same object** in memory, which is stricter than `==` (equal contents). Two lists can be equal but not identical:\n",
"\n",
"```python\n",
"lst1 = [1, 2]\n",
"lst2 = lst1.copy()\n",
"lst1 is lst2 # False - different objects\n",
"lst1 == lst2 # True - same contents\n",
"id(lst1), id(lst2) # different memory addresses\n",
"```\n",
"\n",
"That is why `cmps1.H2O is cmps1.Water` above returns `True`: the synonym does not create a new object, it points to the existing one.\n",
"\n",
" "
]
},
{
"cell_type": "markdown",
"id": "d1bbfa05",
"metadata": {},
"source": [
"### 3.1. Selecting components by property \n",
"Compiling does more than freeze the property order — it also builds boolean (1/0) arrays, aligned with `CompiledComponents.IDs`, that flag each component by phase, biodegradability, and origin. These let you pick out groups of components without writing loops. Common ones are `.g` (dissolved gas), `.s` (soluble), `.c` (colloidal), `.x` (particulate), `.b` (biodegradable), `.rb` (readily biodegradable), `.org` (organic), and `.inorg` (inorganic)."
]
},
{
"cell_type": "code",
"execution_count": 36,
"id": "377e5a5e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0])"
]
},
"execution_count": 36,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"cmps3 = qs.Components.load_default()\n",
"cmps3.g # 1 for each dissolved gas, 0 otherwise"
]
},
{
"cell_type": "code",
"execution_count": 37,
"id": "55240aca",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"('S_H2', 'S_CH4', 'S_N2', 'S_O2')"
]
},
"execution_count": 37,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Translate a 1/0 array into the component IDs it selects\n",
"cmps3.get_IDs_from_array(cmps3.g)"
]
},
{
"cell_type": "code",
"execution_count": 38,
"id": "0f4329b6",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0])"
]
},
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# ...and the other way, from a set of IDs to an aligned 1/0 array\n",
"cmps3.get_array_from_IDs(('S_H2', 'S_CH4', 'S_N2', 'S_O2'))"
]
},
{
"cell_type": "markdown",
"id": "s32md",
"metadata": {},
"source": [
"### 3.2. Defining component groups \n",
"A *group* is a named subset of components. QSDsan uses groups to compute composite variables: the built-in `TKN` group, for instance, powers `WasteStream.TKN` and `composite(subgroup=cmps.TKN)` (see [3. WasteStream](https://qsdsan.readthedocs.io/en/latest/tutorials/3_WasteStream.html)). Define your own with `define_group`, then use the name anywhere a subgroup is accepted."
]
},
{
"cell_type": "code",
"execution_count": 39,
"id": "s32code",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['S_Ac', 'S_Prop']"
]
},
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Group components under a name, then reuse it (here, the volatile fatty acids)\n",
"cmps3.define_group('VFAs', IDs=('S_Ac', 'S_Prop'))\n",
"[c.ID for c in cmps3.VFAs]"
]
},
{
"cell_type": "markdown",
"id": "nav-footer-2_component",
"metadata": {},
"source": [
"\n",
"\n",
"---\n",
"\n",
"↑ Back to top\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.7"
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 5
}