Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecated filter = callback signature (block, *value) #80

Merged
merged 4 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gdplib/mod_hens/modular_discrete.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def build_modular_option(cafaro_approx, num_stages):
"""
m = build_model(cafaro_approx, num_stages)

# Optimize for the least cost configuration across all stages and matche
# Optimize for the least cost configuration across all stages and matches
for hot, cold in m.valid_matches:
lowest_price = float('inf')
for size in sorted(m.possible_sizes, reverse=True):
Expand Down
165 changes: 145 additions & 20 deletions gdplib/water_network/wnd.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
The user can create each instance like this:

build_model(approximation='none')
build_model(approximation='quadratic')
build_model(approximation='quadratic_nonzero_origin')
build_model(approximation='quadratic_zero_origin')
build_model(approximation='piecewise')

The general model description can be summarized as follows:
Expand Down Expand Up @@ -83,7 +84,7 @@
# TU mass balances in the disjunct.


def build_model(approximation='quadratic'):
def build_model(approximation='none'):
"""
Builds a Pyomo ConcreteModel for Water Network Design.
Generates a Pyomo model for the water network design problem with the specified approximation for the capital cost term of the active treatment units.
Expand Down Expand Up @@ -135,16 +136,40 @@ def build_model(approximation='quadratic'):

m.mixers = pyo.Set(doc="Set of mixers", within=m.units, initialize=['dm'] | m.inTU)

def _streams_filter(m, val):
"""
This function filters the streams based on one-to-one port pairing.
The expression re.findall(r'\d+', x) returns a list of all the digits in the string x.
The expression re.findall(r'\d+', y) returns a list of all the digits in the string y.
The function returns True if the ports digits are the same, False otherwise.

Parameters
----------
m : Pyomo concrete model
GDP model for water network design
val : tuple
Tuple of inlet and outlet ports, x are the source ports and y are the sink ports

Returns
-------
Boolean
True if the ports digits are the same, False otherwise
"""
x, y = val
return re.findall(r'\d+', x) == re.findall(r'\d+', y)

m.MU_TU_streams = pyo.Set(
doc="MU to TU 1-1 port pairing",
initialize=m.inTU * m.TU,
filter=lambda _, x, y: re.findall(r'\d+', x) == re.findall(r'\d+', y),
filter=_streams_filter,
# filter=lambda _, x, y: re.findall(r'\d+', x) == re.findall(r'\d+', y),
)

m.TU_SU_streams = pyo.Set(
doc="TU to SU 1-1 port pairing",
initialize=m.TU * m.outTU,
filter=lambda _, x, y: re.findall(r'\d+', x) == re.findall(r'\d+', y),
filter=_streams_filter,
# filter=lambda _, x, y: re.findall(r'\d+', x) == re.findall(r'\d+', y),
)

m.TU_streams = pyo.Set(
Expand All @@ -155,7 +180,8 @@ def build_model(approximation='quadratic'):
m.feed_streams = pyo.Set(
doc="Feed to FSU 1-1 port pairing",
initialize=m.feed * m.FSU,
filter=lambda _, x, y: re.findall(r'\d+', x) == re.findall(r'\d+', y),
filter=_streams_filter,
# filter=lambda _, x, y: re.findall(r'\d+', x) == re.findall(r'\d+', y),
)

m.streams = pyo.Set(
Expand All @@ -168,17 +194,65 @@ def build_model(approximation='quadratic'):
| [('dm', 'sink')],
)

def _from_stream_filter(m, val):
"""
This function filters the streams based on the ports.
The function returns True if the inlet port is a splitter or the inlet and outlet ports are the discharge mixer and the sink, respectively, False otherwise.

Parameters
----------
m : Pyomo concrete model
GDP model for water network design
x : str
The source port
y : str
The sink port

Returns
-------
Boolean
True if the inlet port is a splitter or the inlet and outlet ports are the discharge mixer and the sink, respectively, False otherwise
"""
x, y = val
return x in m.splitters or (x, y) == ('dm', 'sink')

m.from_splitters = pyo.Set(
doc='Streams from splitters',
within=m.streams,
initialize=m.streams,
filter=lambda _, x, y: x in m.splitters or (x, y) == ('dm', 'sink'),
)
filter=_from_stream_filter,
# filter=lambda _, x, y: x in m.splitters or (x, y) == ('dm', 'sink'),
tristantc marked this conversation as resolved.
Show resolved Hide resolved
) # Update the 'filter=' callback to match the signature (block, value).

def _to_stream_filter(m, val):
"""
This function filters the streams based on the ports.
The function returns True if the outlet port is a splitter or the inlet and outlet ports are part of the feed streams, False otherwise.
The feed streams are the streams from the feed splitters to the mixers.

Parameters
----------
m : Pyomo concrete model
GDP model for water network design
x : str
The source port
y : str
The sink port

Returns
-------
Boolean
True if the outlet port is a splitter or the inlet and outlet ports are part of the feed streams, False otherwise
"""
x, y = val
return y in m.splitters or (x, y) in m.feed_streams

m.to_splitters = pyo.Set(
doc='Streams to splitters',
within=m.streams,
initialize=m.streams,
filter=lambda _, x, y: y in m.splitters or (x, y) in m.feed_streams,
filter=_to_stream_filter,
# filter=lambda _, x, y: y in m.splitters or (x, y) in m.feed_streams,
tristantc marked this conversation as resolved.
Show resolved Hide resolved
)

# =============================================================================
Expand Down Expand Up @@ -227,7 +301,6 @@ def build_model(approximation='quadratic'):

# Concentration of component j in feedstream i
for j, i, k in m.contaminant * (m.FSU * ['dm'] | m.FSU * m.inTU | m.feed_streams):
# print(j,i,k)
if i in m.feed:
m.conc[j, i, k].fix(feed.loc[k, j])
else:
Expand Down Expand Up @@ -460,7 +533,7 @@ def _no_cost(disj):
@m.Disjunction(m.TU)
def unit_exists_or_not(m, unit):
'''Disjunction: Unit exists or not.
This disjunctiont specifies if the treatment unit exists or does not exist.
This disjunction specifies if the treatment unit exists or does not exist.

Parameters
----------
Expand Down Expand Up @@ -490,16 +563,64 @@ def unit_exists_or_not(m, unit):

for unit in m.TU:
unit_exists = m.unit_exists[unit]

def _unit_exists_streams_filter(unit_exists, val):
"""
This function filters the streams based on the ports.
The function returns True if the ports are the treatment unit, False otherwise.

Parameters
----------
unit_exists : Pyomo disjunct
Disjunct for the active treatment unit
x : str
The source port
y : str
The sink port

Returns
-------
Boolean
True if either the source or destination port is the treatment unit, False otherwise
"""
x, y = val
return x == unit or y == unit

unit_exists.streams = pyo.Set(
doc="Streams in active TU",
initialize=m.TU_streams,
filter=lambda _, x, y: x == unit or y == unit,
)
filter=_unit_exists_streams_filter,
# filter=lambda _, x, y: x == unit or y == unit,
) # Update the 'filter=' callback to match the signature (block, value).

def _unit_exists_onetoone_filter(unit_exists, val):
"""
This function filters the streams based on the ports.
The function returns True if the ports are the same and the destination port is the treatment unit, False otherwise.

Parameters
----------
unit_exists : Pyomo disjunct
Disjunct for the active treatment unit
x : str
The source port
y : str
The sink port

Returns
-------
Boolean
True if the port if source and destination are the same and the destination is the treatment unit, False otherwise
"""
x, y = val
return re.findall(r'\d+', x) == re.findall(r'\d+', y) and y == unit

unit_exists.MU_TU_streams = pyo.Set(
doc="MU to TU 1-1 port pairing",
initialize=m.inTU * m.TU,
filter=lambda _, x, y: re.findall(r'\d+', x) == re.findall(r'\d+', y)
and y == unit,
filter=_unit_exists_onetoone_filter,
# filter=lambda _, x, y: re.findall(r'\d+', x) == re.findall(r'\d+', y)
tristantc marked this conversation as resolved.
Show resolved Hide resolved
# and y == unit,
)

unit_exists.flow = pyo.Var(
Expand Down Expand Up @@ -579,7 +700,7 @@ def unit_exists_or_not(m, unit):
]
# Setting inlet flowrate bounds for the active treatment units.
unit_exists.flow_bound = pyo.ConstraintList(
doc='Flowrate bounds to/from active RU'
doc='Flowrate bounds to/from active TU'
)
[
unit_exists.flow_bound.add(
Expand Down Expand Up @@ -786,10 +907,14 @@ def _func(model, i, j, xp):
@unit_exists.Constraint(doc='Cost active TU')
def costTU(unit_exists):
"""Constraint: Cost of active treatment unit.
The constraint ensures that the cost of the active treatment unit is equal to the sum of an investment cost which is proportional to the total flow to 0.7 exponent and an operating cost which is proportional to the flow.
If approximation is quadratic, the investment cost is approximated by a quadratic function of the flow rate.
The constraint defines the cost of the active treatment unit as the sum of an investment cost and an operating cost.
The investment cost is proportional to the total flow raised to the power of 0.7 and the operating cost is proportional to the flow.

Based on the approximation given, the concave investment cost is calculated as follows:
If approximation is quadratic zero origin, the investment cost is approximated by a quadratic function of the flow rate with the origin at zero.
If approximation is quadratic nonzero origin, the investment cost is approximated by a quadratic function of the flow rate with origin different from zero. This approximation has a better fit than the quadratic zero origin.
If approximation is piecewise, the investment cost is approximated by a piecewise linear function of the flow rate.
If approximation is none, the investment cost is equal to the flow rate to the 0.7 exponent, the original concave function.
If approximation is none, the investment cost is equal to the flow rate raised to 0.7, the original concave function.

Parameters
----------
Expand All @@ -802,9 +927,9 @@ def costTU(unit_exists):
The constraint that the cost of the active treatment unit is equal to the sum of an investment cost and an operating cost.
"""
for mt, t in unit_exists.streams:
if approximation == 'quadratic':
if approximation == 'quadratic_zero_origin':
new_var = unit_exists.cost_var[unit]
elif approximation == 'quadratic2':
elif approximation == 'quadratic_nonzero_origin':
new_var = _g(unit_exists.flow[mt, unit])
elif approximation == 'piecewise':
new_var = unit_exists.cost_var[mt, unit]
Expand Down
Loading