Change Log¶
This document records notable changes to QSDsan. We aim to follow Semantic Versioning.
1.5.3¶
Added runnable docstring examples to the
Excretion,PitLatrine, andTruckingsanitation units (previously theirExamplessections only linked to EXPOsan), and a supportingqsdsan.utils.create_example_sanitation_components()helper that builds the excreta/nutrient component set those units need (it is not part ofComponents.load_default). Also fixed theIncineratordocstring example, whoseComponent.from_chemical('S_CO2', search_ID='CO2', ...)call passed asearch_IDkeyword thatfrom_chemicaldoes not accept (it was silently swallowed by**data, so the chemical defaulted to the ID'S_CO2'and the lookup raisedLookupError); it now uses the supportedchemical='CO2'form. These examples run underpytest --doctest-modules(already exercised in CI via thepytest.ini--doctest-modulesdefault).Extended the docstring-example coverage to more built-in unit operations. Added runnable
Examplesto the sanitation/static unitsMURT,UDDT,CropApplication,Lagoon,SepticTank,DryingBed,LiquidTreatmentBed,Screening,Sedimentation, andSludgePasteurization, and to the wastewater-treatment unitsActivatedSludgeProcess,BiogasCombustion,CombinedHeatPower,AnaerobicBaffledReactor,AnaerobicDigestion,SludgeThickening,BeltThickener, andSludgeCentrifuge. Addedqsdsan.utils.create_example_wwt_components()(a lumped COD-based component set with theactive_biomass/inert_biomass/substratesgroups those units expect) and extendedqsdsan.utils.create_example_sanitation_components()withStruvite,HAP,MagnesiumHydroxide, andLPGso the struvite-recovery and pasteurization examples run. The BioSTEAM-wrapper units inqsdsan.unit_operations.bstnow all link to the upstream BioSTEAM API documentation from theirSee Alsosections (theTankfamily gained the links the others already had). All examples run underpytest --doctest-modules.Added a new section on operational flexibility to
tutorials/6_System.ipynb(section 4): anAgileSystemexample that runs an example treatment plant in two seasonal operation modes and compiles one annualized TEA and LCA across them, each mode weighted by its operating hours. Tutorials 7 (TEA) and 8 (LCA) gained cross-references to it.Added a new section on controlling recycle convergence to
tutorials/6_System.ipynb(section 5): how to read a non-convergence report, theSystemtolerance settings (molar_tolerance,relative_molar_tolerance,temperature_tolerance,relative_temperature_tolerance),maxiter, and the accelerationmethodwith their live defaults, and what to adjust when a recycle loop will not converge, demonstrated on the tutorial’s own example system. The §1 recycle-convergence note was corrected to referencerelative_molar_toleranceandtemperature_tolerance(the priorrmol_toleranceis not a real attribute) and now links to the new section. The recycle-convergence pitfall intutorials/14_Modeling_Notes_and_Pitfalls.ipynb(§2.2) deep-links to it, namesmaxiter, and had its fix example corrected (themolar_tolerance/temperature_tolerancevalues shown were tighter than the defaults, contradicting the “loosen” advice; they now sit above the defaults). Tutorial 11 (§1.1) notes thatatol/rtol(andprint_msg) forward toscipy.integrate.solve_ivpfor dynamic-integration accuracy and diagnostics.Fixed
TEAso that net present value and other replacement-cost calculations work for anAgileSystem. The per-unit capital objects of an agile system expose their equipment lifetime asequipment_lifetime(a single unit useslifetime); the replacement-cost loop now reads the correct attribute instead of raisingAttributeError.Fixed equipment and construction lifetimes so they reach
equipment_lifetime, the attribute that TEA and LCA actually read.SanUnitresetequipment_lifetimeto{}at construction (discarding BioSTEAM’s copy of the class-level_default_equipment_lifetime), andadd_equipment_design,add_construction, and several units (ActivatedSludgeProcess,AnaerobicReactor,MembraneBioreactor,PolishingFilter,WWTpump) wrote lifetimes into_default_equipment_lifetime, which nothing read back. As a result every built-in equipment/construction lifetime was silently dropped, so equipment replacement costs (TEA) and construction-replacement impacts (LCA) were never charged.SanUnitnow preserves the class-level lifetimes and routes equipment/construction lifetimes into the instanceequipment_lifetime(no longer mutating the shared class dict). This changes TEA/LCA results for systems whose equipment lifetime is shorter than the project lifetime (the replacement is now charged); on the EXPOsan test systems the shift is within existing tolerances (for example, ~0.6% on a pump-bearing reclaimer TEA). Addedtests/test_equipment_lifetime.py. Tutorial 7 (section 1.4) and thelifetimedocstrings now use and recommendlifetime(the alias for the BioSTEAMequipment_lifetimeattribute) as the name for a unit’s equipment lifetime.Added
tutorials/14_Modeling_Notes_and_Pitfalls.ipynbcovering common modeling surprises across streams/components, unit design, TEA/LCA, and behavior inherited from BioSTEAM. Extended tutorial 5 §1.1 withsimulate()/_summarycall-graph mechanics.Expanded process-specification coverage in the tutorials. Tutorial 5 §3.7 now explains what
run=Truedoes (re-running_runso a spec-changed parameter reaches the outlets) and the degrees-of-freedom intuition (one adjustable knob per target). Its exampleAerobicReactor3previously cached its reaction at__init__and ignored laterconversionchanges, so the spec was a silent no-op;_runnow syncsaerobic_rxn.Xto the liveconversion, and the example reports the resulting effluent COD so the effect is visible. Tutorial 6 gains a new §3 on system-level specifications (System.add_specificationandadd_bounded_numerical_specification), driving a parameter until a system outlet hits a target, with cross-references between the two sections.Deepened the cost and design coverage in the SanUnit (advanced) tutorial (notebook 5). The
costdecorator section now documents thelb/ub/Nparallel-unit behavior, thelifetimeand customfarguments, and stacking multiple@costdecorators on one class. A new §3.3 on auxiliary units shows how a unit can own another unit operation (e.g., aHXutilityheat exchanger) via theauxiliary_unit_namesclass attribute, with its design, cost, and utilities rolling up into the parent automatically; theEquipmentsection now contrasts the two mechanisms. Later §3 subsections were renumbered accordingly.Restructured the FAQ into a four-page
faq/subdirectory (errors,tips,styling,ai_assisted_coding), absorbing the previously standalone AI-assisted coding page from the tutorials section. The oldFAQ.htmlbookmark redirects to the new index.Fixed
DynamicInfluentfor non-cyclic input data: when the first and last rows of the data file differed, the constructor entered a branch that calleddf.append(y_end, ignore_index=True)to pad the time series with a phantom final point. The line was always a silent no-op (DataFrame.appendreturned a new frame rather than modifying in place; the result was discarded), and starting with pandas 2.0appendwas removed entirely, so the same call now raisesAttributeError: 'DataFrame' object has no attribute 'append'and the constructor fails outright. The branch now usespd.concatand reassignsself._dataso the phantom point is actually appended;self._t_endand the interpolants pick it up. Default-file behavior is unchanged because the shipped_inf_dry_2006.tsvis cyclic (first row equals last row) and the branch is skipped.In EXPOsan, restored the documented
exposan.bsm1.cmps/components/asmmodule attributes (the canonical handles used by the dynamic-simulation tutorial).bsm1.load()declared the three namesglobalbut the matching assignment was commented out, so after a freshload()they stayedNoneandqs.set_thermo(bsm1.cmps)raisedTypeError: 'NoneType' object is not iterable. The assignments (cmps = components = O1.components,asm = O1.suspended_growth_model) are now active.Added a public
dynamic_parametersaccessor onProcessandCompiledProcessesthat returns the dictionary of attachedDynamicParameterobjects (keyed by symbol; empty when all parameters are static). This mirrors the existingparametersproperty, so users no longer have to reach into the private_dyn_paramsattribute to inspect what dynamic parameters a process has. Added test coverage intests/test_process.py.load_from_file()now supports per-process conservation rules through three composable forms. Previously every process in a batch-built model shared oneconserved_fortuple, forcing users to fall back to per-process manual construction whenever (e.g.) decay shouldn’t be required to conserve carbon.conserved_foris now keyword-only and required (no default value) and accepts: a tuple (uniform — applied to every process, unchanged from before), a dict keyed by process ID (per-process rules; IDs absent from the dict fall back to('COD', 'N', 'P', 'charge')), or None (defers to a new optionalconserved_forcolumn in the data file, which carries comma-separated material names per row; empty cells mean no enforcement). The file column is consumed during parsing and does not appear in the stoichiometry matrix. Resolution order, highest first: kwarg-dict entry for the listed process ID → kwarg-tuple (uniform) → file column → default tuple. Backward incompatibility: existing callers that omittedconserved_for(relying on the previous tuple default) will now raiseTypeErrorand must pass an explicit value. The tutorial’s example datafile is now split into_bkm.csv(no column, demonstrates the tuple-then-dict workflow) and_bkm_with_conserved.csv(column-bearing, demonstratesconserved_for=None). Added test coverage intests/test_process.py.Fixed the SanUnit-mixin plumbing on eight
bst-namespace wrappers (BinaryDistillation,ShortcutColumn,MESHDistillation,AdiabaticMultiStageVLEColumn,Flash,IsothermalCompressor,ProcessWaterCenter,HeatExchangerNetwork) whose MRO put the BioSTEAM parent ahead ofSanUnit(or, forHeatExchangerNetwork, explicitly overwrote__init__).SanUnit.__init__never ran, so the LCA/add-on attributes (construction,transportation,equipment,add_OPEX,uptime_ratio,lifetime,include_construction) silently did not exist on instances of these classes, and kwargs likelifetime=oradd_OPEX=were dropped. Each affected wrapper now defines an__init__that calls the BioSTEAM parent and thenself._init_sanunit_addons(...)(a new method extracted fromSanUnit.__init__so the mixin-install logic has a single source of truth). Added a three-layer test for everybstclass intests/test_bst_units.py(smoke + parity-where-feasible + add-on persistence throughsimulate()) so any future mixin regression fails CI rather than silently propagating.API: harmonized the six plotting helpers in
qsdsan.statsso users can place a plot on a chosen axis and forward styling kwargs uniformly. Everyplot_*now acceptsax=Noneand**plot_kws(forwarded to the primary underlying plotting call, i.e., theseabornfunction or the matplotlibscatter/bar/errorbar). Onplot_uncertainties, the existingcenter_kws/margin_kwsare kept because the 2Dseaborn.JointGridhas two axes;plot_kwsmerges intocenter_kws(center_kwswins on conflict). Onplot_uncertainties2D plots and onplot_correlations,ax=is documented as ignored because the underlying calls build their own figure.plot_correlations’s old**kwargswas renamed to**plot_kws; existing callers that pass named kwargs (every caller surveyed in EXPOsan and the tutorials) are unaffected. Also fixed a latent bug in the bar-only branch ofplot_sobol_resultsthat was dropping the user-suppliedax=.Made
qsdsana more self-contained namespace so users typically don’t need toimport biosteam/import thermosteamdirectly. Newly re-exported at the top level:settings,preferences,stream_utility_prices,Thermo,UtilityAgent,Facility,AgileSystem,get_OSBL,MissingStream, and thereportsubmodule.qsdsan.unit_operations.bstnow also surfaces the BioSTEAM units QSDsan does not customize (IsenthalpicValve,Stripper,MolecularSieve,BatchBioreactor,VacuumSystem,Boiler,BoilerTurbogenerator,ChilledWaterPackage,CoolingTower,SolidsCentrifuge), andqsdsan.utilsnow re-exportsrho_to_V,V_to_rho, the@costdecorator, andvar_columns/var_indices. A new Public API documentation page lists the full surface, andtests/test_public_api.pyasserts each re-export still matches its BioSTEAM/Thermosteam source (so an upstream rename fails loudly in CI rather than reaching a user).Fixed
get_unit_annualized_equipment_cost()(used byannualized_equipment_cost): when a unit declared per-equipment lifetimes through anequipment_lifetimedict, the loop variable shadowed the cost accumulator, so the annualized equipment cost was computed incorrectly (the running sum was dropped and the bare-module factors mis-applied). It now iterates each unit’sinstalled_costswith a separate variable, annualizing every item over its own lifetime (falling back to the unit or TEA lifetime). Addedtests/test_tea.pycovering this case along with the otherTEAcost metrics.Fixed
qsdsan.utils.indices.ChemPPI_by_year: the 2021 and 2022 entries had been mistakenly populated with CEPCI values (708.8, 816.0). They have been removed, so the chemical-PPI series now ends at 2020 until authentic values are added.qsdsan.utilsnow thinly wraps BioSTEAM’s@costdecorator so it also acceptsCEPCIas an alias for the reference cost-index argumentCE(e.g.,@cost(..., CEPCI=522)), for consistency withqsdsan.CEPCI/qsdsan.CEPCI_by_year. BioSTEAM’sCEkeyword continues to work unchanged.Fixed compatibility with newer Thermosteam releases that make
Chemical.MWa read-only property:Componentcreation assignedself.MW = 1.as a placeholder for components without a formula or molecular weight, which raisedAttributeError: cannot set molecular weight. The placeholder is now set viaqsdsan._compat.set_chemical_MW, which uses the publicMWsetter when it is writable and falls back to Thermosteam’s internal constant-reset helper when it is read-only (so it works on both old and new Thermosteam,>=0.53.4). This unblockedComponents.load_defaultand therefore nearly every system build.Unified report generation across
System,TEA, andLCA: callingsave_reporton any of the three now produces the same Excel workbook (system design, costs, and utilities, plus the QSDsan LCA tables on anLCAsheet whenever the system has anLCA). This works by wrapping BioSTEAM’sSystem.save_reportat import (qsdsan.Systemis BioSTEAM’sSystem, andTEA.save_reportalready delegates to it); systems without an LCA behave exactly as before.save_report()now delegates to the system report, and its default filename changed from{system.ID}_lca.xlsxto{system.ID}_report.xlsx; for the LCA tables alone, useget_impact_table(). Addedtests/test_lca.pycovering the unified output. In EXPOsan,bwaise’ssave_reportsno longer writes a separate LCA file.Added
qsdsan.utils.create_example_treatment_systems, which builds the aerobic and anaerobic wastewater treatment systems shared by the TEA and LCA tutorials (a compact, realistic substrate for techno-economic and life cycle analyses). The LCA tutorial now uses it instead of thebwaiseEXPOsan system.Added section 5.6 (Cost and environmental trade-off) to
tutorials/8_LCA.ipynb: a single table lining up the break-even treatment fee (cost per m³, from tutorial 7) against the GWP per m³ for the two example plants, so the cost-versus-carbon trade-off is visible in one place, with a note on comparing systems on the same functional unit and system boundary.Added a
time_frameargument to theLCAresults methods (get_total_impacts(),get_unit_impacts(),get_impact_table(),get_allocated_impacts()/get_allocated_impact_table(), andsave_report). It normalizes the results to a chosen time frame:'lifetime'(or'all', the default),'yr'(equivalent toannual=True),'month','week','day', or'hr'. The existingannualflag is kept as a backward-compatible alias (annual=True≡time_frame='yr');time_frametakes precedence when both are given.Added
get_normalized_impacts(), which expresses impacts per functional unit (per kg, per m³, or per MJ) of one or more reference streams, the LCA counterpart toTEA.solve_price. By default the total impacts are divided by the streams’ combined throughput; passallocate_byto normalize the impacts allocated to those streams instead.Fixed
get_unit_impacts(): stream impacts were added twice (the accumulator was initialized to the stream-impact dict and then the stream impacts were added again), overstating the result. Each category is now counted once.Fixed
get_allocated_impacts()(and the newget_allocated_impact_table()): passing a function forallocate_by(a documented option) raisedTypeErrorbecausecallablewas tested afteriter(allocate_by), which fails on a non-iterable function.callableis now checked first, so a function returning the allocation ratios works; an invalidallocate_bynow raises a clearValueError.Added
get_allocated_impact_table(), the tabular counterpart ofget_allocated_impacts()(impacts allocated to two or more streams, one row per stream, indicator columns, plus an'Allocation factor'column). The unified report can include it as an opt-in'LCA allocation'sheet by passinglca_allocate_streams(and optionallylca_allocate_by) tosave_report. The shared allocation-ratio logic was factored into a private helper used by both methods.Fixed
get_impact_table(): the per-categorySumrow was written via pandas chained assignment, which silently no-ops under Copy-on-Write (pandas ≥ 2.x), so the total row came out blank andChainedAssignmentErrorwarnings were emitted. The row totals are now assigned with.loc.Fixed
get_impact_table()for empty'Stream'and'Other'categories: building the total row on an empty table assigned a float into a column pandas 3.0 had inferred as the string dtype, raisingTypeError: Invalid value ... for dtype 'str'. These categories now return a'No ...-related impacts.'message when empty (matching the existing'Construction'/'Transportation'behavior), which is also skipped when writing the report.Fixed
add_other_item(): when given a string ID with no matchingImpactItem, the raisedValueErrorreportedNone(the failed-lookup result) instead of the requested ID. It now names the missing ID.Testing: added a
conftest.pyautouse fixture that resets the LCA registries and auto-ID ticket counters before each doctest, so the LCA doctests no longer leak state into one another (e.g., an indicator alias or auto-generated ID carrying over) when the modules run together.In EXPOsan, replaced the per-test
clear_lca_registries()calls (deprecated) with an autouseconftest.pyfixture that resets the LCA registries before each test.Documentation: expanded the tutorials with new sections on defining component groups (
Components.define_group), unit specifications (add_specification), and inferring aSystemfrom a list of units (System.from_units), plus notes on flowsheet retrieval, recycle convergence, and exporting results.Documentation: fixed dark-mode rendering of DataFrame tables and
stderr(warning) output, standardized.diagramusage with a cross-reference between the System and Dynamic Simulation tutorials, and repaired a broken hyperlink.Documentation: concluded the topical-tutorial polish pass. Tutorial 12 (Anaerobic Digestion Model No. 1) gained a new §1 with hand-authored light/dark SVG pairs of the ADM1 reaction network and DAE structure (under
docs/source/images/adm1/), cross-tutorial connections to tutorials 10 and 11, and a data-grounded §3 discussion of the t = 10 d start-up transient (acidified plateau, biomass split into growing vs. declining groups). Tutorial 10 (Process) also gained a short Petersen-matrix paragraph that tutorial 12 references. Tutorial 13 (Process Modeling 101) was repositioned as the flowsheet-scale companion to tutorials 10-12: §1 was rewritten, cross-tutorial connections were added at the top of each §2 subsection (Component → tutorial 2, WasteStream → tutorial 3, Process → tutorial 10, SanUnit → tutorials 4/5/11, System → tutorials 6/11), §3 gained prose on the integrator-plus-recycle-convergence loop and on interpreting the steady-state output (SRT ≈ 10 d at the design point; nitrification / denitrification signatures; KLa-driven dissolved oxygen), and the prerequisites and learning objectives were updated to match. Tutorial 13’s seven embedded image attachments were extracted toassets/tutorial_13/as snake_case files, clearing a Sphinximage.not_readablewarning that the URL-encoded%20paths produced. TwoRun ?su.Xruntime-help prompts in tutorial 13 were replaced with documentation links into the autodoc-generated API pages (with the?su.Xform retained as a parenthetical fallback).
1.5.2¶
Fixed packaging:
qsdsan/units_of_measure.txt(the pint unit-definition file loaded at import) was not declared inpackage-data, so non-editable installs (wheels) omitted it andimport qsdsanraisedFileNotFoundError. It is now included in the distributed package.Fixed
copy_like(): when called withcopy_price=Trueorcopy_impact_item=True, the price/impact item were copied in the wrong direction (overwriting the source stream and leaving the target unchanged). They are now correctly copied from the source into the target.Added doctest examples to
copy(),copy_like(), andcopy_flow()documenting what each method copies (flows, temperature/pressure, price, and impact item).Added
.. warning::notes topHandSAlkclarifying that these are not calculated from stream composition (no acid-base model yet) and should be treated as user-provided inputs.Fixed
HXutility: its_unitsdictionary was inadvertently set toNone(it was assigned the return value ofdict.update, which is alwaysNone), which brokeresults()with anAttributeErrorand mutated BioSTEAM’s shared_units. The unit-of-measure entries are now built with a dict merge.Re-exported the Thermosteam reaction classes (
Reaction,ReactionItem,ReactionSet,ParallelReaction,SeriesReaction,ReactionSystem, and theRxn/RxnI/RxnS/PRxn/SRxn/RxnSysaliases) from the top-levelqsdsannamespace, so they can be imported withfrom qsdsan import Reactioninstead of reaching into BioSTEAM/Thermosteam.Added
qsdsan.CEPCI, a settable view of the global Chemical Engineering Plant Cost Index (which BioSTEAM abbreviates asCE), so the costing index can be read and set (e.g.,qsdsan.CEPCI = qsdsan.CEPCI_by_year[2023]) without importing biosteam.Fixed
TEA: itsCEPCIargument defaulted tobst.CE, which Python binds once at import time (freezing it at 567.5). EveryTEAcreated without an explicitCEPCItherefore reset the global cost index, silently overriding a deliberately setqsdsan.CEPCI/bst.CE.CEPCInow defaults toNone(the current index is left untouched), and a providedCEPCIis applied before simulation so it actually affects costing.CEPCI_by_yearnow returnsqsdsan.CEPCI_by_year(was BioSTEAM’s table) for consistency, andqsdsan.CEPCI_by_yearnow merges in BioSTEAM’sdesign_tools.CEPCI_by_yearyears not already present (e.g., pre-1990), with qsdsan’s more precise values taking precedence on overlapping years. QSDsan’s built-in hydroprocessing/hydrothermal units now source their@costreference indices fromqsdsan.CEPCI_by_yearas well (a sub-0.1% cost change from the more precise values).In EXPOsan, systems that set the global cost index (
htlandsaf) now do so viaqsdsan.CEPCI = ...instead ofbst.CE = ..., for consistency with the newqsdsan.CEPCIhandle (functionally identical, sinceqsdsan.CEPCIis a live view ofbst.CE).Renamed the cost-index dicts in
qsdsan.utils.indicesand the keys ofqsdsan.utils.tea_indicesto the*_by_yearconvention ('CEPCI_by_year','ChemPPI_by_year','labor_by_year','PCEPI_by_year'). Update any code that used the old short keys, e.g.,tea_indices['CEPCI']becomestea_indices['CEPCI_by_year'].Tutorial updates ongoing.
1.5.1¶
Componentnow validates theparticle_size,degradability, andorganicarguments at creation, for both the constructor andfrom_chemical(). Invalid values (e.g., a misspelledparticle_size) that were previously accepted silently now raise aValueError.The
f_BOD5_COD,f_uBOD_COD, andf_Vmass_Totmassfractions are now range-checked to[0, 1](this check was previously unreachable).The
Componentconstructor now accepts achemicalkeyword to build a component directly from an existingthermosteam.Chemical.from_chemical()is now a thin wrapper around it, with unchanged behavior.Documented the
ignore_inaccurate_molar_weightandadjust_MW_to_measured_asoptions ofcompile(), and substantially revised the topical tutorials.
1.5.0¶
All LCA registry types (
ImpactIndicator,ImpactItem,Construction,Transportation) are now isolated per flowsheet. Switching between systems viaset_flowsheet()atomically swaps all four registries, so no manualclear_lca_registries()calls are needed between systems.clear_lca_registries()is deprecated.Added
SanUnit._construction_specs— a class-level tuple of dicts for declaring default construction materials. Specs are resolved lazily byLCAat creation time, soImpactItemobjects do not need to exist when the unit is instantiated.Reorganized unit operations and process models into clearer namespaces.
qsdsan.sanunitsis renamed toqsdsan.unit_operationsand reorganized into three behavior-based sub-namespaces:qsdsan.unit_operations.bst— BioSTEAM-inherited unit operations (mixers, splitters, pumps, heat exchangers, distillation columns, tanks, etc.)qsdsan.unit_operations.static— steady-state QSDsan unit operations (sanitation fixtures, treatment beds, clarifiers, sludge handling, hydrothermal/hydroprocessing units, etc.)qsdsan.unit_operations.dynamic— unit operations with explicit dynamic-state behavior (bioreactors, dynamic influent, junctions, membrane bioreactors, etc.)
qsdsan.processesis renamed toqsdsan.process_models.All existing imports via
qsdsan.sanunitsandqsdsan.processesremain valid for backward compatibility.
Restructured API documentation to mirror the new package layout, with dedicated pages for each sub-namespace.
Bug fixes in
rhos_asm2d():Added
if X_MeOH > 0andif X_MeP > 0guards to the precipitation and redissolution reactions, consistent with the existing guards forX_H,X_PAO, andX_AUT. Without these guards, the BDF solver’s polynomial extrapolation could produce tiny positive floating-point values forX_MeOH, causing spuriousX_MePaccumulation over long simulations.Added a
S_F + S_A > 0guard to the heterotrophic growth substrate-partitioning terms. When the BDF Newton iterations drive both fermentable substrate (S_F) and acetate (S_A) to zero simultaneously, the partition fractionsS_F/(S_F+S_A)andS_A/(S_F+S_A)produce0/0and raise aFloatingPointError; zero substrate correctly implies zero growth.
Multiple bug fixes and improvements to
PolishingFilter:Moved O2 deficit calculation before the effluent split so dissolved oxygen is correctly accounted for in all outlet streams.
Added an aerobic-only guard for air injection: air is now only added when
self.has_pumpisFalseand the unit operates in aerobic mode.Fixed a missing
_freeboardattribute that causedAttributeErroron initialization.Restored the
_design_anaerobicmethod that had been inadvertently removed.Corrected the slab concrete volume formula.
Added a
biomass_IDparameter to allow users to specify which component tracks active biomass.Renamed internal attributes
gas/soluble/solidtogases/solubles/solidsfor consistency with the rest of the codebase.Fixed the condition in
get_digestion_rxnsand corrected the argument order in_refresh_rxns.Fixed a
SanStream.degassingAttributeErrorthat occurred when the polishing filter effluent was a plainSanStream.Fixed an O2 double-counting error in
air_outthat produced a ~0.6% mass-balance error.
Fixed
HydraulicDelay: added missing_update_stateand_update_dstatemethods so the unit correctly propagates state during dynamic simulation.Added a deprecation warning to
SimpleTEAto guide users toward the updated TEA interface.Lazy-imported optional heavy dependencies (
SALib,seaborn,chaospy,sympy) so thatimport qsdsanno longer pays the startup cost of those libraries unless they are actually used.Added a GitHub Actions release workflow that automatically publishes to PyPI and creates a GitHub release when a
v*.*.*tag is pushed.
1.4.0¶
A lot of the updates have been focused on the dynamic simulation, now the open-loop Benchmark Simulation Model No. 2 (BSM2) configuration has been implemented with new process models and unit operation including
qsdsan.processes.ADM1pqsdsan.processes.ADM1_p_extensionqsdsan.processes.ModifiedADM1qsdsan.processes.mASM2dqsdsan.sanunits.IdealClarifierqsdsan.sanunits.PrimaryClarifierqsdsan.sanunits.PrimaryClarifierBSM2qsdsan.sanunits.GasExtractionMembraneqsdsan.sanunits.Thickenerqsdsan.sanunits.Centrifugeqsdsan.sanunits.Incineratorqsdsan.sanunits.BatchExperimentqsdsan.sanunits.PFRqsdsan.sanunits.BeltThickenerqsdsan.sanunits.SludgeCentrifugeqsdsan.sanunits.SludgeThickener
New publications
Feng et al., Environmental Science & Technology, on the sustainability of hydrothermal liquefaction (HTL) for resource recovery from a range of wet organic wastes.
1.3.0¶
Enhance and use QSDsan’s capacity for dynamic simulation for emerging technologies and benchmark configurations (see EXPOsan METAB and PM2 (on the algae branch, still under development) modules).
New publications
The paper introducing DMsan, the package developed for decision-making of sanitation and resource recovery technologies, is published in ACS Environmental Au!
QSDsan was used to evaluate the sustainability of the NEWgenerator system as in this paper on ACS Environmental Au!
New modules
qsdsan.processes.KineticReaction
QSDsannow has a website to host all of the resources!QSDsan’s documentation is getting a new look!Add new units to enable dynamic simulation of systems with multiple process models. Check out
qsdsan.sanunits.Junction,qsdsan.sanunits.ADMtoASM,qsdsan.sanunits.ASMtoADMand their use in the interface system demo.In online testing, we dropped the test for Python 3.8 and added Python 3.10. The main developing environment for QSDsan is 3.9.
1.2.0¶
The QSDsan paper is accepted by Environmental Science: Water Research & Technology!
The first paper using QSDsan for the design of sanitation is accepted by ACS Environmental Au! Read the Biogenic Refinery paper and check out the system module in
QSDsan/EXPOsan.Added multiple systems (including their unit operations), check out the details on the Developed System page!
Biogenic Refinery
Eco-San
Reclaimer
Added the anaerobic digestion model no. 1 (ADM1) process model and the unit
qsdsan.sanunits.AnaerobicCSTR, the corresponding system can be found in EXPOsan.Other new unit operations:
Encapsulation Bioreactors:
qsdsan.sanunits.CH4Eqsdsan.sanunits.H2E
1.1.0¶
Fully tested dynamic simulation capacity, refer to the BSM1 system in EXPOsan for an example implementation.
Added many new
qsdsan.SanUnitand reorganized package/documentation structure, new unit operations include:qsdsan.sanunits.AnMBRqsdsan.sanunits.CHPqsdsan.sanunits.InternalCirculationRxqsdsan.sanunits.SludgeHandlingqsdsan.sanunits.BeltThickenerqsdsan.sanunits.SludgeCentrifuge
qsdsan.sanunits.PolishingFilterqsdsan.sanunits.WWTpump
Continue to enhance documentation (e.g.,
qsdsan.Process, qsdsan.stats, util functions).
1.0.0¶
Official release of QSDsan v1.0.0!
Added system-wise dynamic simulation capacity. To use the dynamic simulation function, a unit needs to have several supporting methods to initialize its state and compile ordinary differential equations (ODEs), refer to the units included in the BSM1 system below for usage, documentation and tutorial will be coming soon!
Developed the benchmark simulation system no.1 (BSM1) model on EXPOsan with comparison against the MATLAB/Simulink model developed by the International Water Association (IWA) Task Group on Benchmarking of Control Strategies. See the README for details
Significantly expanded the tutorials with demo videos on YouTube. Now tutorials cover all non-dynamic major classes (tutorials on dynamic classes will be included in the next major release).
0.3.0¶
Now LCA data can be imported from external databases using the newly made BW2QSD package.
New subclasses of
qsdsan.SanUnit:qsdsan.sanunits.Clarifierqsdsan.sanunits.CSTRqsdsan.sanunits.ElectrochemicalCellusing the followingqsdsan.Equipment:
New subclasses of
qsdsan.Process:qsdsan.processes.DiffusedAerationqsdsan.processes.ASM1qsdsan.processes.ASM2d
Updated
qsdsan.SanUnitso that it can be initialized with any ofthermosteam.Stream,qsdsan.SanStream, orqsdsan.WasteStream.These three classes can now be mixed.
Added
qsdsan.SanStreamfor non-waste streams (e.g., gases).Updated the
add_OPEXattribute ofqsdsan.SanUnitandsystem_add_OPEXattribute ofqsdsan.SimpleTEAso that they takedictas the default to allow display of multiple additional operating expenses.Split the
systemsmodule into an individual package EXPOsan.Now using
thermosteam.utils.Registryto manageqsdsan.ImpactIndicatorandqsdsan.ImpactItem.Added AppVeyor CI.
Renamed the
masterbranch tomain.
0.2.0¶
Added
qsdsan.Process,qsdsan.Processes, andqsdsan.CompiledProcessesclasses for stoichiometric process and its kinetics.Added an
qsdsan.Equipmentclass for design and costing of unit equipment.For the
statsmodule:More statistical tests:
qsdsan.stats.fast_analysis()for (extended) Fourier amplitude sensitivity test (FAST) and random balance design (RBD) FAST.qsdsan.stats.morris_till_convergence()to run Morris analysis until the results converge.Added Kendall’s tau and Kolmogorov–Smirnov test to
qsdsan.stats.get_correlations().
Plotting functions to visualize all test results:
qsdsan.stats.plot_uncertainties()for results from uncertainty analysis as different 1D or 2D plots.qsdsan.stats.plot_correlations()for results fromqsdsan.stats.get_correlation().Bar plot option for
qsdsan.stats.plot_morris_results().qsdsan.stats.plot_morris_convergence()to plot \({\mu^*}\) against the number of trajectories.qsdsan.stats.plot_fast_results()for results from FAST and/or RBD-FAST analyses.qsdsan.stats.plot_sobol_results()for results from Sobol analysis.
Changed all .csv data files to .tsv so that they can be viewed on GitHub.
Added more clear guidelines on contribution and a author list in the document.
0.1.0¶
Added a
statsmodule including:Pearson and Spearman correlations:
qsdsan.stats.get_correlations().Morris One-at-A-Time (OAT) screening method:
qsdsan.stats.morris_analysis().Also added a function for plotting:
qsdsan.stats.plot_morris_results().
Sobol sensitivity analysis:
qsdsan.stats.sobol_analysis().
Added all uncertainty parameters for all of the scenarios in the bwaise system, also added demonstrative Morris and Sobol analysis.
LCA.get_normalized_impacts()was replaced byqsdsan.LCA.get_allocated_impacts()forqsdsan.LCAto enable flexible allocation options.Reformatted all documents, added instructions on documentation.
Added brief instructions on contributing and code of conduct.
Updated UML diagram.
0.0.3¶
More flexible setting of
qsdsan.ImpactItemforqsdsan.WasteStream.Add status badge to README.rst
Add CHANGELOG.rst
Tutorial updates:
- New:
qsdsan.TEAandqsdsan.LCA
- Updated:
qsdsan.SanUnitandqsdsan.System
0.0.2¶
Added the all three sanitation scenarios as described in Trimmer et al., including uncertainty/sensitivity analyses with tutorial.
Inclusion of GPX models for estimation of
qsdsan.WasteStreamproperties.New classes:
All units in Trimmer et al.
Added descriptors (
qsdsan.utils.descriptors) and decorators (qsdsan.utils.checkers) to check user-input values.qsdsan.utils.setters.AttrSetter,qsdsan.utils.setters.DictAttrSetter, andqsdsan.utils.getters.FuncGetterfor batch-setting of uncertainty analysis parameters.
Added
save_report()function toqsdsan.LCAfor report exporting.
0.0.1¶
First public release.