Skip to content

Design input feature - #263

Draft
yoelcortes wants to merge 9 commits into
masterfrom
design_input
Draft

yoelcortes wants to merge 9 commits into
masterfrom
design_input

Conversation

@yoelcortes

@yoelcortes yoelcortes commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

@yalinli2, @sarangbhagwat,

This pull is huge, but don't let that scare you; it's actually just one new function design_input that provides a standard way to define input parameters for unit operations. The main benefits include defaults, value checks, auto-assigning cost factors (e.g., material factors), automatic documentation, and have them appear in <Unit>.results().

It generates Python property objects (with default getter/setter) but you can also define the getter/setter yourself. Here is an example:

import biosteam as bst

class StirredTank(bst.Unit):
    """Stirred tank with validated design inputs."""

    _N_ins = 1
    _N_outs = 1
    _F_BM_default = {'Vessel': 2.0}
    _units = {'Volume': 'm3'}

    # Numerical design inputs
    tau = bst.design_input(
        'Residence time', 'hr', lb=0
    )
    T = bst.design_input(
        'Operating temperature', 'K', lb=273.15, default=350,
    )
    P = bst.design_input(
        'Operating pressure', 'Pa', lb=0, default=101325,
    )

    # Categorical design input
    orientation = bst.design_input(
        catalog={'Vertical', 'Horizontal'}
    )

    # Categorical input with automatic cost factors
    material = bst.design_input(
        'Construction material',
        catalog={
            'Carbon steel': 1.0,
            'Stainless steel': 2.5,
        },
        purchase_item='Vessel',
        catalog_type='Material',
        default='Carbon steel',
    )

    # Custom getter and setter
    @bst.design_input('Working volume fraction', lb=0, ub=1, default=0.8)
    def V_wf(self):
        return self._volume_fraction
    @V_wf.setter
    def V_wf(self, value):
        self._volume_fraction = value

    # No need to set any parameters, their values are automatically set using the default (if given).
    def _init(self, orientation='Vertical'): 
        self.orientation = orientation

    def _run(self):
        self.outs[0].copy_like(self.ins[0])

    def _design(self):
        volume = self.ins[0].F_vol * self.tau / self.V_wf
        self.design_results['Volume'] = volume

    def _cost(self):
        volume = self.design_results['Volume']
        self.baseline_purchase_costs['Vessel'] = (
            10000 * volume ** 0.6
        )

# Create and simulate the tank
bst.settings.set_thermo(['Water'])
T1 = StirredTank(
    'T1', ins=bst.Stream('feed', Water=1000),
    tau=4, V_wf=0.9,
    orientation='Vertical',
    material='Stainless steel',
)

# Material cost factor is assigned automatically
assert T1.F_M['Vessel'] == 2.5

# Changing the material updates the factor
T1.material = 'Carbon steel'
assert T1.F_M['Vessel'] == 1.0

# Numerical bounds and categorical options are validated
# T1.tau = -1              # Raises AttributeError
# T1.V_wf = 1.5            # Raises AttributeError
# T1.material = 'Titanium' # Raises AttributeError

# Results include design inputs
T1.simulate()
print(T1.results())
# Stirred tank                                       Units            T1
# Design inputs            Operating pressure           Pa        101325
#                          Operating temperature         K           350
#                          Working volume fraction                   0.9
#                          Construction material            Carbon steel
#                          Orientation                          Vertical
#                          Residence time               hr             4
# Design results           Volume                       m3          80.3
# Purchase cost            Vessel                      USD      1.39e+05
# Total purchase cost                                  USD      1.39e+05
# Installed equipment cost                             USD      2.78e+05
# Utility cost                                      USD/hr             0

# Retrieve design inputs in different units
assert T1.get_design_input('tau', 'hr') == 4
assert T1.get_design_input('tau', 'min') == 240

# Set design inputs in different units
T1.set_design_input('tau', 'min', 300)
assert T1.tau == 5  # Converted to hr

# Custom getters and setters are supported
T1.set_design_input('V_wf', '', 0.85)
assert T1.V_wf == 0.85

The documentation looks like this:

design_input P: float
Operating pressure [Pa]. Defaults to 101325 Pa.

design_input T: float
Operating temperature [K]. Defaults to 350 K.

design_input V_wf: float
Working volume fraction. Defaults to 0.8.

design_input material: str
Construction material. Defaults to 'Carbon steel'

design_input orientation: str
Orientation. Defaults to 'Vertical'

design_input tau: float
Residence time [hr].

Here is chatgpt's summary of the key features:

  • Defaults: Define and validate default values.
  • Numerical inputs: Define engineering units and enforce lower and upper bounds.
  • Categorical inputs: Restrict selections to predefined options, such as equipment materials or configurations.
  • Cost integration: Automatically update equipment cost factors based on categorical selections.
  • Documentation: Automatically generate property-style documentation compatible with Sphinx.
  • Inheritance: Automatically recognize design inputs across unit operation subclasses and mixins.

@yalinli2

Copy link
Copy Markdown
Member

I'll take a look over the weekend. Interesting feature, I'll test it with QSDsan to see if there's any updates needed.

On a not-so-relevant note, is there checks on hensmith to make sure updates on biosteam/thermosteam won't break it? My weekly CI on QSDsan suggests that hensmith isn't compatible with the new updates in thermosteam (because of the removal of the network feature I think, haven't taken a deeper look). I can open an issue on hensmith

@yoelcortes

Copy link
Copy Markdown
Member Author

@yalinli2, OK!

Yeah, hensmith broke with the new update. I will fix the issue with biosteam.

Heads up, I also updated the code in Unit.init, so we will need to update QSDsan to enable the default feature for design_input.

Thanks,

@yoelcortes
yoelcortes marked this pull request as draft November 12, 2026 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants