Processes#
Processes convert a set of resources into another. The balance of this conversion is provided by the user (see section Conversion under the Defining Resources Tutorial). A few examples are list:
General Setup#
Consider the processes, Wind Farm (wf) and Proton Exchange Membrane Electrolysis (pem) These require the resource wind, power, water (h2o), hydrogen (h2), oxygen (o2)
wind and h2o can be consumed, there is a demand for hydrogen and oxygen can be released
from energia import Currency, Process, Resource, Model, Periods
m = Model()
m.usd = Currency()
m.q = Periods()
m.y = 4 * m.q
m.declare(Resource, ['wind', 'power', 'h2o', 'h2', 'o2'])
m.declare(Process, ['wf', 'pem'])
m.wind.consume == True
m.h2o.consume == True
m.h2.release.prep(180) >= [0.6, 0.7, 0.8, 0.3]
m.o2.release == True
⚖ Initiated wind balance in (l0, y) ⏱ 0.0001 s
⚖ Initiated h2o balance in (l0, y) ⏱ 0.0001 s
⚖ Initiated h2 balance in (l0, q) ⏱ 0.0001 s
🔗 Bound [≥] h2 release in (l0, q) ⏱ 0.0008 s
⚖ Initiated o2 balance in (l0, y) ⏱ 0.0001 s
With Positive Basis#
The basis is provided in the format Process(Resource | Conversion) For example, in the case of wind farm the basis is provided as power produced. Thus all the values for usd.spend need to be provided on the basis of power production
m.wf(m.power) == -m.wind
m.wf.operate.prep(norm=True) <= [0.9, 0.8, 0.5, 0.7]
m.wf.capacity[m.usd.spend] == 990637 + 3354
m.wf.operate[m.usd.spend] == 49
🔗 Bound [≤] wf operate in (l0, q) ⏱ 0.0005 s
🔗 Bound [=] usd spend in (l0, y) ⏱ 0.0002 s
🧭 Mapped time for operate (wf, l0, q) ⟺ (wf, l0, y) ⏱ 0.0002 s
🔗 Bound [=] usd spend in (l0, y) ⏱ 0.0003 s
With Negative Basis (or Conversion)#
PEMs are typically sized based on their power consumption, thus all the values provide should be on the basis of power expended in the process
m.pem(-m.power) == 0.01248 * m.h2 + 0.09987 * m.o2 - 0.11235 * m.h2o
m.pem.capacity[m.usd.spend] == 1.55 * 10**6
🔗 Bound [=] usd spend in (l0, y) ⏱ 0.0003 s
Locating Processes#
Processes need to located explicitly
If using multiple locations, the user can use m.location.locate(… list of processes …)
For single period models, it may be convenient to use m.locate(… list of processes …) which defaults to m.network.locate(…). Note that in a single location example, the location itself is the network
m.locate(m.pem, m.wf)
💡 Assumed pem capacity unbounded in (l0, y) ⏱ 0.0001 s
🔗 Bound [≤] pem operate in (l0, y) ⏱ 0.0003 s
💡 Assumed pem operate bounded by capacity in (l0, y) ⏱ 0.0007 s
🧭 Mapped time for operate (pem, l0, q) ⟺ (pem, l0, y) ⏱ 0.0002 s
⚖ Initiated power balance in (l0, q) ⏱ 0.0001 s
🔗 Bound [=] power expend in (l0, q) ⏱ 0.0010 s
⚖ Updated h2 balance with produce(h2, l0, q, operate, pem) ⏱ 0.0001 s
🔗 Bound [=] h2 produce in (l0, q) ⏱ 0.0008 s
⚖ Updated o2 balance with produce(o2, l0, y, operate, pem) ⏱ 0.0001 s
🔗 Bound [=] o2 produce in (l0, y) ⏱ 0.0007 s
⚖ Updated h2o balance with expend(h2o, l0, y, operate, pem) ⏱ 0.0001 s
🔗 Bound [=] h2o expend in (l0, y) ⏱ 0.0007 s
🏭 Operating streams introduced for pem in l0 ⏱ 0.0060 s
🏗 Construction streams introduced for pem in l0 ⏱ 0.0000 s
🌍 Located pem in l0 ⏱ 0.0081 s
💡 Assumed wf capacity unbounded in (l0, y) ⏱ 0.0001 s
💡 Assumed wf operate bounded by capacity in (l0, q) ⏱ 0.0001 s
⚖ Updated power balance with produce(power, l0, q, operate, wf) ⏱ 0.0002 s
🔗 Bound [=] power produce in (l0, q) ⏱ 0.0011 s
⚖ Updated wind balance with expend(wind, l0, y, operate, wf) ⏱ 0.0001 s
🔗 Bound [=] wind expend in (l0, y) ⏱ 0.0010 s
🏭 Operating streams introduced for wf in l0 ⏱ 0.0035 s
🏗 Construction streams introduced for wf in l0 ⏱ 0.0000 s
🌍 Located wf in l0 ⏱ 0.0048 s
The balances arising from production are only assumed once the process has beeen located. Upon location, the model can be optimized
m.usd.spend.opt()
m.capacity.output()
🧭 Mapped samples for spend (usd, l0, y, capacity, wf) ⟺ (usd, l0, y) ⏱ 0.0003 s
🧭 Mapped samples for spend (usd, l0, y, operate, wf) ⟺ (usd, l0, y) ⏱ 0.0002 s
🧭 Mapped samples for spend (usd, l0, y, capacity, pem) ⟺ (usd, l0, y) ⏱ 0.0002 s
📝 Generated Program(m).mps ⏱ 0.0022 s
Set parameter Username
Academic license - for non-commercial use only - expires 2026-08-01
Read MPS format model from file Program(m).mps
Reading time = 0.00 seconds
PROGRAM(M): 41 rows, 38 columns, 82 nonzeros
📝 Generated gurobipy model. See .formulation ⏱ 0.0074 s
Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (win64 - Windows 11.0 (26100.2))
CPU model: 13th Gen Intel(R) Core(TM) i7-13700, instruction set [SSE2|AVX|AVX2]
Thread count: 16 physical cores, 24 logical processors, using up to 24 threads
Optimize a model with 41 rows, 38 columns and 82 nonzeros
Model fingerprint: 0x88a70d85
Coefficient statistics:
Matrix range [1e-02, 2e+06]
Objective range [1e+00, 1e+00]
Bounds range [0e+00, 0e+00]
RHS range [6e-01, 2e+02]
Presolve removed 29 rows and 22 columns
Presolve time: 0.00s
Solved in 0 iterations and 0.00 seconds (0.00 work units)
Infeasible model
🛑 No solution found. Check the model 🛑
Modeling Technology Choice#
Let us continue with the example, but provide another option for power generation (solar PV)
m.solar = Resource()
m.solar.consume == True
m.pv = Process()
m.pv(m.power) == -1 * m.solar
m.pv.capacity.x <= 30000
m.pv.capacity.x >= 0
m.pv.operate.prep(norm=True) <= [0.6, 0.8, 0.9, 0.7]
m.pv.capacity[m.usd.spend] == 5670
m.pv.operate[m.usd.spend] == 90
m.locate(m.pv)
⚖ Initiated solar balance in (l0, y) ⏱ 0.0001 s
🔗 Bound [≤] pv capacity in (l0, y) ⏱ 0.0003 s
🔗 Bound [≥] pv capacity in (l0, y) ⏱ 0.0002 s
🔗 Bound [≤] pv operate in (l0, q) ⏱ 0.0007 s
🔗 Bound [=] usd spend in (l0, y) ⏱ 0.0004 s
🧭 Mapped time for operate (pv, l0, q) ⟺ (pv, l0, y) ⏱ 0.0002 s
🔗 Bound [=] usd spend in (l0, y) ⏱ 0.0004 s
💡 Assumed pv capacity unbounded in (l0, y) ⏱ 0.0001 s
💡 Assumed pv operate bounded by capacity in (l0, q) ⏱ 0.0001 s
⚖ Updated power balance with produce(power, l0, q, operate, pv) ⏱ 0.0002 s
🔗 Bound [=] power produce in (l0, q) ⏱ 0.0012 s
⚖ Updated solar balance with expend(solar, l0, y, operate, pv) ⏱ 0.0001 s
🔗 Bound [=] solar expend in (l0, y) ⏱ 0.0011 s
🏭 Operating streams introduced for pv in l0 ⏱ 0.0033 s
🏗 Construction streams introduced for pv in l0 ⏱ 0.0000 s
🌍 Located pv in l0 ⏱ 0.0047 s
Semi-continuous Bounds#
These can be set using .x for any aspect. In the below example, the maximum capacity of wind farm (if set up) is 30000 The lower bound of 500 is only applied if wind farm is set up at all. Thus the values that wind farm can take are in the semi-continuous domain \(\{0\} \cup [500, 30000]\)
m.wf.capacity.x <= 30000
m.wf.capacity.x >= 500
🔗 Bound [≤] wf capacity in (l0, y) ⏱ 0.0002 s
🔗 Bound [≥] wf capacity in (l0, y) ⏱ 0.0002 s
m.usd.spend.opt()
📝 Generated Program(m).mps ⏱ 0.0048 s
Warning: row name O0 in column section at line 146 not defined.
Warning: row name C57 in column section at line 150 not defined.
Read MPS format model from file Program(m).mps
Reading time = 0.00 seconds
PROGRAM(M): 57 rows, 54 columns, 121 nonzeros
📝 Generated gurobipy model. See .formulation ⏱ 0.0079 s
Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (win64 - Windows 11.0 (26100.2))
CPU model: 13th Gen Intel(R) Core(TM) i7-13700, instruction set [SSE2|AVX|AVX2]
Thread count: 16 physical cores, 24 logical processors, using up to 24 threads
Optimize a model with 57 rows, 54 columns and 121 nonzeros
Model fingerprint: 0x04105377
Variable types: 52 continuous, 2 integer (2 binary)
Coefficient statistics:
Matrix range [1e-02, 2e+06]
Objective range [1e+00, 1e+00]
Bounds range [1e+00, 1e+00]
RHS range [6e-01, 2e+02]
Presolve removed 57 rows and 54 columns
Presolve time: 0.00s
Presolve: All rows and columns removed
Explored 0 nodes (0 simplex iterations) in 0.01 seconds (0.00 work units)
Thread count was 1 (of 24 available processors)
Solution count 1: 6.70673e+10
Optimal solution found (tolerance 1.00e-04)
Best objective 6.706730769231e+10, best bound 6.706730769231e+10, gap 0.0000%
📝 Generated Solution object for Program(m). See .solution ⏱ 0.0004 s
✅ Program(m) optimized using gurobi. Display using .output() ⏱ 0.0180 s
The solution for the two models can be compared
m.capacity.output(compare=True)
Note: binaries were only introduced in the second model
m.capacity.reporting.output(compare=True)