def __new__(cls, ID): self = super().__new__(cls) #: [Register] Contains all System objects as attributes. self.system = Registry() #: [Register] Contains all Unit objects as attributes. self.unit = Registry() #: [Register] Contains all Stream objects as attributes. self.stream = Registry() #: [str] ID of flowsheet. self._ID = ID self.flowsheet.__dict__[ID] = self return self
class Flowsheet: """ Create a Flowsheet object which stores references to all stream, unit, and system objects. For a tutorial on flowsheets, visit :doc:`tutorial/Managing_flowsheets`. """ line = "Flowsheet" #: [Flowsheets] All flowsheets. flowsheet = Flowsheets() def __new__(cls, ID): self = super().__new__(cls) #: [Register] Contains all System objects as attributes. self.system = Registry() #: [Register] Contains all Unit objects as attributes. self.unit = Registry() #: [Register] Contains all Stream objects as attributes. self.stream = Registry() #: [str] ID of flowsheet. self._ID = ID self.flowsheet.__dict__[ID] = self return self def __reduce__(self): return self.from_registries, self.registries def __setattr__(self, key, value): if hasattr(self, '_ID'): raise TypeError(f"'{type(self).__name__}' object does not support attribute assignment") else: super().__setattr__(key, value) @property def ID(self): return self._ID @classmethod def from_registries(cls, stream, unit, system): flowsheet = super().__new__(cls) flowsheet.stream = stream flowsheet.unit = unit flowsheet.system = system return flowsheet @property def registries(self): return (self.stream, self.unit, self.system) def clear(self, reset_ticket_numbers=True): for registry in self.registries: registry.clear() self.flowsheet.clear() if reset_ticket_numbers: for i in (Stream, Unit, System): i.ticket_numbers.clear() def discard(self, ID): for registry in self.registries: if ID in registry: registry.discard(ID) return def remove_unit_and_associated_streams(self, ID): stream_registry = self.stream unit = self.unit.pop(ID) for inlet in unit._ins: if inlet.source: continue stream_registry.discard(inlet) for outlet in unit._outs: if outlet._sink: continue stream_registry.discard(outlet) def update(self, flowsheet): for registry, other_registry in zip(self.registries, flowsheet.registries): registry.data.update(other_registry.data) def to_dict(self): return {**self.stream.data, **self.unit.data, **self.system.data} @classmethod def from_flowsheets(cls, ID, flowsheets): """Return a new flowsheet with all registered objects from the given flowsheets.""" new = cls(ID) isa = isinstance for flowsheet in flowsheets: if isa(flowsheet, str): flowsheet = cls.flowsheet[flowsheet] new.update(flowsheet) return new def diagram(self, kind=None, file=None, format=None, display=True, number=None, profile=None, label=None, **graph_attrs): """ Display all units and attached streams. Parameters ---------- kind : int or string, optional * 0 or 'cluster': Display all units clustered by system. * 1 or 'thorough': Display every unit within the path. * 2 or 'surface': Display only elements listed in the path. * 3 or 'minimal': Display a single box representing all units. file : str, display in console by default File name to save diagram. format : str File format (e.g. "png", "svg"). Defaults to 'png'. display : bool, optional Whether to display diagram in console or to return the graphviz object. number : bool, optional Whether to number unit operations according to their order in the system path. profile : bool, optional Whether to clock the simulation time of unit operations. label : bool, optional Whether to label the ID of streams with sources and sinks. """ return self.create_system(None).diagram(kind or 'thorough', file, format, display, number, profile, label, **graph_attrs) def create_system(self, ID="", feeds=None, ends=(), facility_recycle=None, operating_hours=None, lang_factor=None): """ Create a System object from all units and streams defined in the flowsheet. Parameters ---------- ID : str, optional Name of system. feeds : Iterable[:class:`~thermosteam.Stream`], optional All feeds to the system. Specify this argument if only a section of the complete system is wanted as it may disregard some units. ends : Iterable[:class:`~thermosteam.Stream`], optional End streams of the system which are not products. Specify this argument if only a section of the complete system is wanted, or if recycle streams should be ignored. facility_recycle : :class:`~thermosteam.Stream`, optional Recycle stream between facilities and system path. This argument defaults to the outlet of a BlowdownMixer facility (if any). operating_hours : float, optional Number of operating hours in a year. This parameter is used to compute convinience properties such as utility cost and material cost on a per year basis. lang_factor : float, optional Lang factor for getting fixed capital investment from total purchase cost. If no lang factor, installed equipment costs are estimated using bare module factors. """ return System.from_units(ID, self.unit, feeds, ends, facility_recycle, operating_hours, lang_factor) def create_network(self, feeds=None, ends=()): """ Create a Network object from all units and streams defined in the flowsheet. Parameters ---------- feeds : Iterable[:class:`~thermosteam.Stream`] Feeds to the process. ends : Iterable[:class:`~thermosteam.Stream`] End streams of the system which are not products. Specify this argument if only a section of the system is wanted, or if recycle streams should be ignored. """ feeds = feeds_from_units(self.unit) if feeds: sort_feeds_big_to_small(feeds) feedstock, *feeds = feeds network = Network.from_feedstock(feedstock, feeds, ends) else: network = Network([]) return network def __call__(self, ID): """ Return requested biosteam item or all items with given Unit subclass. Parameters ---------- ID : str or type ID of the requested item or Unit subclass. """ isa = isinstance if isa(ID, str): ID = ID.replace(' ', '_') obj = (self.stream.search(ID) or self.unit.search(ID) or self.system.search(ID)) if not obj: raise LookupError(f"no registered item '{ID}'") return obj elif issubclass(ID, bst.Unit): cls = ID return [i for i in self.unit if isa(i, cls)] else: raise ValueError('ID must be either a string or a Unit subclass') def __str__(self): return self.ID def __repr__(self): return f'<{type(self).__name__}: {self.ID}>'
class Flowsheet: """ Create a Flowsheet object which stores references to all stream, unit, and system objects. For a tutorial on flowsheets, visit :doc:`tutorial/Managing_flowsheets`. """ line = "Flowsheet" #: [Register] All flowsheets. flowsheet = Flowsheets() def __new__(cls, ID): self = super().__new__(cls) #: [Register] Contains all System objects as attributes. self.system = Registry() #: [Register] Contains all Unit objects as attributes. self.unit = Registry() #: [Register] Contains all Stream objects as attributes. self.stream = Registry() #: [str] ID of flowsheet. self._ID = ID self.flowsheet.__dict__[ID] = self return self def __reduce__(self): return self.from_registries, self.registries def __setattr__(self, key, value): if hasattr(self, '_ID'): raise TypeError( f"'{type(self).__name__}' object does not support attribute assignment" ) else: super().__setattr__(key, value) def view(self): """ Create an interactive process flowsheet diagram that autorefreshes itself. """ widget = FlowsheetWidget(self) widget.show() return widget @property def ID(self): return self._ID @classmethod def from_registries(cls, stream, unit, system): flowsheet = super().__new__(cls) flowsheet.stream = stream flowsheet.unit = unit flowsheet.system = system return flowsheet @property def registries(self): return (self.stream, self.unit, self.system) def discard(self, ID): for registry in self.registries: if ID in registry: registry.discard(ID) break def update(self, flowsheet): for registry, other_registry in zip(self.registries, flowsheet.registries): registry.__dict__.update(other_registry.__dict__) @classmethod def from_flowsheets(cls, ID, flowsheets): """Return a new flowsheet with all registered objects from the given flowsheets.""" new = cls(ID) isa = isinstance for flowsheet in flowsheets: if isa(flowsheet, str): flowsheet = cls.flowsheet[flowsheet] new.update(flowsheet) return new def diagram(self, kind='surface', file=None, format='svg', **graph_attrs): """Display all units and attached streams. Parameters ---------- kind='surface' : Must be one of the following * **'thorough':** Thoroughly display every unit. * **'surface':** Display units and recycle systems. * **'minimal':** Minimally display flowsheet as a box with feeds and products. """ if kind == 'thorough': f = digraph_from_units_and_streams(self.unit, self.stream, format=format, **graph_attrs) elif kind == 'surface': f = self._surface_digraph(format, **graph_attrs) elif kind == 'minimal': f = minimal_digraph(self.ID, self.units, self.streams, **graph_attrs) else: raise ValueError( f"kind must be either 'thorough', 'surface', or 'minimal'.") finalize_digraph(f, file, format) def _surface_digraph(self, format, **graph_attrs): surface_units = set(self.unit) old_unit_connections = set() for i in self.system: if i.recycle and not any(sub.recycle for sub in i.subsystems): surface_units.difference_update(i.units) update_surface_units(i.ID, i.streams, i.units, surface_units, old_unit_connections) f = digraph_from_units(surface_units) for u, ins, outs in old_unit_connections: u._ins[:] = ins u._outs[:] = outs return f def create_system(self, ID="", feeds=None, ends=(), facility_recycle=None): """ Create a System object from all units and streams defined in the flowsheet. Parameters ---------- ID : str, optional Name of system. feeds : Iterable[:class:`thermosteam.Stream`], optional Feeds to the process ordered in decreasing priority. The first feed should be your feedstock. ends : Iterable[:class:`~thermosteam.Stream`], optional End streams of the system which are not products. Specify this argument if only a section of the system is wanted, or if recycle streams should be ignored. facility_recycle : [:class:`~thermosteam.Stream`], optional Recycle stream between facilities and system path. This argument defaults to the outlet of a BlowdownMixer facility (if any). """ if not feeds: feeds = get_feeds_from_streams(self.stream) sort_feeds_big_to_small(feeds) if feeds: feedstock, *feeds = feeds facilities = self.get_facilities() if not facility_recycle: facility_recycle = find_blowdown_recycle(facilities) system = System.from_feedstock(ID, feedstock, feeds, facilities, ends, facility_recycle) else: system = System(ID, ()) return system def create_network(self, feeds=None, ends=()): """ Create a Network object from all units and streams defined in the flowsheet. Parameters ---------- ends : Iterable[:class:`~thermosteam.Stream`] End streams of the system which are not products. Specify this argument if only a section of the system is wanted, or if recycle streams should be ignored. """ feeds = get_feeds_from_streams(self.stream) if feeds: sort_feeds_big_to_small(feeds) feedstock, *feeds = feeds network = Network.from_feedstock(feedstock, feeds, ends) else: network = Network([]) return network def create_path(self, feeds=None, ends=()): isa = isinstance network = self.create_network(feeds, ends) net2sys = System.from_network return tuple([(net2sys('', i) if isa(i, Network) else i) for i in network.path]) def get_facilities(self): isa = isinstance return [i for i in self.unit if isa(i, Facility)] def __call__(self, ID): """ Return requested biosteam item. Parameters ---------- ID : str ID of the requested item. """ ID = ID.replace(' ', '_') obj = (self.stream.search(ID) or self.unit.search(ID) or self.system.search(ID)) if not obj: raise LookupError(f"no registered item '{ID}'") return obj def __str__(self): return self.ID def __repr__(self): return f'<{type(self).__name__}: {self.ID}>'
class Flowsheet: """ Create a Flowsheet object which stores references to all stream, unit, and system objects. For a tutorial on flowsheets, visit :doc:`tutorial/Managing_flowsheets`. """ line = "Flowsheet" #: [Register] All flowsheets. flowsheet = Flowsheets() def __new__(cls, ID): self = super().__new__(cls) #: [Register] Contains all System objects as attributes. self.system = Registry() #: [Register] Contains all Unit objects as attributes. self.unit = Registry() #: [Register] Contains all Stream objects as attributes. self.stream = Registry() #: [str] ID of flowsheet. self._ID = ID self.flowsheet.__dict__[ID] = self return self def __reduce__(self): return self.from_registries, self.registries def __setattr__(self, key, value): if hasattr(self, '_ID'): raise TypeError( f"'{type(self).__name__}' object does not support attribute assignment" ) else: super().__setattr__(key, value) @property def ID(self): return self._ID @classmethod def from_registries(cls, stream, unit, system): flowsheet = super().__new__(cls) flowsheet.stream = stream flowsheet.unit = unit flowsheet.system = system return flowsheet @property def registries(self): return (self.stream, self.unit, self.system) def clear(self): for registry in self.registries: registry.clear() def discard(self, ID): for registry in self.registries: if ID in registry: registry.discard(ID) break def update(self, flowsheet): for registry, other_registry in zip(self.registries, flowsheet.registries): registry.data.update(other_registry.data) def to_dict(self): return {**self.stream.data, **self.unit.data, **self.system.data} @classmethod def from_flowsheets(cls, ID, flowsheets): """Return a new flowsheet with all registered objects from the given flowsheets.""" new = cls(ID) isa = isinstance for flowsheet in flowsheets: if isa(flowsheet, str): flowsheet = cls.flowsheet[flowsheet] new.update(flowsheet) return new def diagram(self, kind=None, file=None, format=None, display=True, **graph_attrs): """ Display all units and attached streams. Parameters ---------- kind='surface' : 'cluster', 'thorough', 'surface', or 'minimal' * **'cluster':** Display all units clustered by system. * **'thorough':** Display every unit within the path. * **'surface':** Display only elements listed in the path. * **'minimal':** Display path as a box. file : str, display in console by default File name to save diagram. format : str File format (e.g. "png", "svg"). Defaults to 'png'. display : bool, optional Whether to display diagram in console or to return the graphviz object. """ return self.create_system(None).diagram(kind or 'thorough', file, format, display) def create_system(self, ID="", feeds=None, ends=(), facility_recycle=None): """ Create a System object from all units and streams defined in the flowsheet. Parameters ---------- ID : str, optional Name of system. feeds : Iterable[:class:`~thermosteam.Stream`], optional All feeds to the system. Specify this argument if only a section of the complete system is wanted as it may disregard some units. ends : Iterable[:class:`~thermosteam.Stream`], optional End streams of the system which are not products. Specify this argument if only a section of the complete system is wanted, or if recycle streams should be ignored. facility_recycle : :class:`~thermosteam.Stream`, optional Recycle stream between facilities and system path. This argument defaults to the outlet of a BlowdownMixer facility (if any). """ return System.from_units(ID, self.unit, feeds, ends, facility_recycle) def create_network(self, feeds=None, ends=()): """ Create a Network object from all units and streams defined in the flowsheet. Parameters ---------- feeds : Iterable[:class:`~thermosteam.Stream`] Feeds to the process. ends : Iterable[:class:`~thermosteam.Stream`] End streams of the system which are not products. Specify this argument if only a section of the system is wanted, or if recycle streams should be ignored. """ feeds = feeds_from_units(self.unit) if feeds: sort_feeds_big_to_small(feeds) feedstock, *feeds = feeds network = Network.from_feedstock(feedstock, feeds, ends) else: network = Network([]) return network def __call__(self, ID): """ Return requested biosteam item. Parameters ---------- ID : str ID of the requested item. """ ID = ID.replace(' ', '_') obj = (self.stream.search(ID) or self.unit.search(ID) or self.system.search(ID)) if not obj: raise LookupError(f"no registered item '{ID}'") return obj def __str__(self): return self.ID def __repr__(self): return f'<{type(self).__name__}: {self.ID}>'