├── .gitignore
├── GITHUB_COMMIT_GUIDE.md
├── LICENSE
├── README.md
├── USEFUL_LINKS.md
├── data
└── README.md
├── img
└── milops-wdn-logo.png
├── lib
└── README.md
├── models
├── OneTankTwoPumps
│ ├── One_Tank_Two_Pump_v1_OPT_Pattern_H_H.inp
│ └── One_Tank_Two_Pump_v1_OPT_Pattern_H_H.net
├── README.md
└── RichmondSkeleton
│ ├── Richmond_skeleton.inp
│ └── Richmond_skeleton_temp.inp
├── src
├── matlab_octave
│ ├── case_studies
│ │ ├── ccwi2023
│ │ │ ├── create_statistics_plots.m
│ │ │ ├── find_ht_mae_outliers.m
│ │ │ ├── run_ccwi_case_study.m
│ │ │ ├── run_cplex.py
│ │ │ └── transform_2p1t_to_milp_ccwi2023.m
│ │ └── two_pump_one_tank
│ │ │ ├── hydraulics_2p1t.m
│ │ │ ├── initialise_2p1t.m
│ │ │ ├── linearize_pipes_2p1t.m
│ │ │ ├── linearize_pump_power_2p1t.m
│ │ │ ├── linearize_pumps_2p1t.m
│ │ │ ├── linprog_to_sim_schedule.m
│ │ │ ├── plot_2p1t_simulation_results.m
│ │ │ ├── plot_elem_flows_demands_2p1t.m
│ │ │ ├── plot_energy_consumption_2p1t.m
│ │ │ ├── plot_linprog_pump_schedules_2p1t.m
│ │ │ ├── plot_nodal_heads_2p1t.m
│ │ │ ├── plot_pump_schedules_2p1t.m
│ │ │ ├── plot_scheduling_2p1t_results.m
│ │ │ ├── set_constraints_2p1t.m
│ │ │ └── simulator_2p1t.m
│ ├── external
│ │ ├── BuildMPS
│ │ │ ├── BuildMPS.m
│ │ │ ├── README.txt
│ │ │ ├── SaveMPS.m
│ │ │ └── license.txt
│ │ └── saveJSONfile
│ │ │ ├── license.txt
│ │ │ └── saveJSONfile.m
│ ├── install_milp_scheduler.m
│ ├── lib
│ │ ├── abs_error.m
│ │ ├── calculate_schedule.m
│ │ ├── combine_incidence_matrices.m
│ │ ├── get_array_indices.m
│ │ ├── get_line.m
│ │ ├── get_line_implicit.m
│ │ ├── get_plane.m
│ │ ├── get_system.m
│ │ ├── lin_index_from_array.m
│ │ ├── lin_index_from_size.m
│ │ ├── number_of_dimensions.m
│ │ ├── perc_error.m
│ │ ├── pipe_characteristic.m
│ │ ├── pump_head.m
│ │ ├── pump_intercept_flow.m
│ │ ├── pump_nominal_point.m
│ │ ├── pump_power_consumption.m
│ │ ├── rem_z_coordinate.m
│ │ ├── remove_columns.m
│ │ ├── remove_rows.m
│ │ ├── sub_from_array.m
│ │ ├── sub_from_size.m
│ │ └── vol_to_h_constant_area.m
│ ├── milp_formulation
│ │ ├── README.md
│ │ ├── equality_constraints
│ │ │ ├── README.md
│ │ │ ├── aa_constraints.m
│ │ │ ├── bb_constraints.m
│ │ │ ├── ht_constraints.m
│ │ │ ├── nodeq_constraints.m
│ │ │ ├── pipe_headloss_constraints.m
│ │ │ ├── pumpgroup_constraints.m
│ │ │ ├── qq_constraints.m
│ │ │ ├── ss_constraints.m
│ │ │ └── ww_constraints.m
│ │ ├── inequality_constraints
│ │ │ ├── README.md
│ │ │ ├── pipe_flow_segment_constraints.m
│ │ │ ├── power_ineq_constraint.m
│ │ │ ├── power_model_ineq_constraint.m
│ │ │ ├── pump_domain_constraints.m
│ │ │ ├── pump_equation_constraints.m
│ │ │ ├── pump_symmetry_breaking.m
│ │ │ ├── q_box_constraints.m
│ │ │ ├── qq_box_constraints.m
│ │ │ ├── s_box_constraints.m
│ │ │ └── ss_box_constraints.m
│ │ ├── lib
│ │ │ ├── apply_constraint.m
│ │ │ ├── constant_bounds.m
│ │ │ ├── create_stacked_triangles.m
│ │ │ ├── create_tangent_surface.m
│ │ │ ├── create_tetrahedron.m
│ │ │ ├── get_inequality.m
│ │ │ ├── inject_vector.m
│ │ │ ├── pump_power_tangent.m
│ │ │ ├── tensor_to_vector.m
│ │ │ └── var_struct_length.m
│ │ ├── linearize_pipe_characteristic.m
│ │ ├── linearize_pipes.m
│ │ ├── linearize_power_model.m
│ │ ├── linearize_power_pumps.m
│ │ ├── linearize_pump_characteristic.m
│ │ ├── linearize_pumps.m
│ │ ├── pump_head_linear.m
│ │ ├── pump_power_linear.m
│ │ ├── set_A_b_matrices.m
│ │ ├── set_Aeq_beq_matrices.m
│ │ ├── set_intcon_vector.m
│ │ ├── set_objective_vector.m
│ │ └── set_variable_bounds.m
│ ├── post_processing
│ │ ├── get_element_flows.m
│ │ ├── get_heads.m
│ │ ├── get_number_working_pumps.m
│ │ ├── get_pump_flows.m
│ │ ├── get_pump_powers.m
│ │ ├── get_pump_speeds.m
│ │ ├── get_tank_levels.m
│ │ └── plot_optim_outputs.m
│ ├── run_2p1t_with_matlab.m
│ ├── run_2p1t_without_matlab_1.m
│ ├── run_2p1t_without_matlab_2.m
│ ├── transform_2p1t_to_milp.m
│ └── variable_structures
│ │ ├── README.md
│ │ ├── find_schedule_from_x.m
│ │ ├── initialise_var_structure.m
│ │ ├── lib
│ │ ├── struct_to_vector.m
│ │ └── vector_to_struct.m
│ │ ├── map_lp_vector_index_to_var.m
│ │ └── map_var_index_to_lp_vector.m
└── python
│ ├── data
│ └── 2p1t
│ │ └── README.md
│ ├── docs
│ ├── run_cplex.py
│ └── solve_with_cplex_cbc.ipynb
│ ├── requirements.txt
│ └── src
│ └── utils
│ ├── __init__.py
│ └── generate_pdf_debug_report.py
└── tests
└── matlab_octave
├── README.md
├── debug_2p1t
├── check_c_vector.m
├── check_equality_constraints.m
├── check_inequality_constraints.m
├── check_intcon_vector.m
├── check_lb_vector.m
├── check_ub_vector.m
├── eq_ineq_constraint_report.m
├── intcon_vector_report.m
├── reports
│ └── README.md
├── run_debug.m
└── vector_report.m
├── run_tests.m
├── test_A_b_creation.m
├── test_Aeq_beq_creation.m
├── test_intcon_vector_creation.m
├── test_obj_vector_creation.m
├── test_pipe_linearization.m
├── test_power_linearization.m
├── test_pump_linearization.m
├── test_rolling_out_vectors.m
└── test_var_intlinprog_vec_mapping.m
/.gitignore:
--------------------------------------------------------------------------------
1 | # C settings
2 | # ---------------------------------------
3 |
4 | # Prerequisites
5 | *.d
6 |
7 | # Object files
8 | *.o
9 | *.ko
10 | *.obj
11 | *.elf
12 |
13 | # Linker output
14 | *.ilk
15 | *.map
16 | *.exp
17 |
18 | # Precompiled Headers
19 | *.gch
20 | *.pch
21 |
22 | # Libraries
23 | *.lib
24 | *.a
25 | *.la
26 | *.lo
27 |
28 | # Shared objects (inc. Windows DLLs)
29 | *.dll
30 | *.so
31 | *.so.*
32 | *.dylib
33 |
34 | # Executables
35 | *.exe
36 | *.out
37 | *.app
38 | *.i*86
39 | *.x86_64
40 | *.hex
41 |
42 | # Debug files
43 | *.dSYM/
44 | *.su
45 | *.idb
46 | *.pdb
47 |
48 | # Kernel Module Compile Results
49 | *.mod*
50 | *.cmd
51 | .tmp_versions/
52 | modules.order
53 | Module.symvers
54 | Mkfile.old
55 | dkms.conf
56 |
57 | bin/
58 |
59 | # MATLAB/OCTAVE/SIMULINK settings
60 | # ---------------------------------------
61 | # Windows default autosave extension
62 | *.asv
63 |
64 | # OSX / *nix default autosave extension
65 | *.m~
66 |
67 | # Compiled MEX binaries (all platforms)
68 | *.mex*
69 |
70 | # Packaged app and toolbox files
71 | *.mlappinstall
72 | *.mltbx
73 |
74 | # Generated helpsearch folders
75 | helpsearch*/
76 |
77 | # Simulink code generation folders
78 | slprj/
79 | sccprj/
80 |
81 | # Matlab code generation folders
82 | codegen/
83 |
84 | # Simulink autosave extension
85 | *.autosave
86 |
87 | # Simulink cache files
88 | *.slxc
89 |
90 | # Octave session info
91 | octave-workspace
92 |
93 | src/matlab/paper_plots/
94 | src/matlab/scheduling_plots/
95 | manuscript/
96 | src/matlab/outtakes_nogit/
97 | plots/
98 | *.mat
99 |
100 | # Debugging reports
101 | *.report
102 |
103 | # Pyenv version files
104 | .python-version
105 |
106 | # Outputs
107 | outputs/
108 |
109 | # Environments
110 | .env
111 | .venv
112 | env/
113 | venv/
114 | ENV/
115 | env.bak/
116 | venv.bak/
117 |
118 | # mypy
119 | .mypy_cache/
120 | .dmypy.json
121 | dmypy.json
122 |
123 | # Pyre type checker
124 | .pyre/
125 |
126 | # Pdf files
127 | *.pdf
128 |
129 | # Jupyter Notebook
130 | .ipynb_checkpoints
131 | **/*.ipynb_checkpoints/*
132 |
133 | # IPython
134 | profile_default/
135 | ipython_config.py
136 |
137 | # pyenv
138 | .python-version
139 |
140 | # Distribution / packaging
141 | .Python
142 | build/
143 | develop-eggs/
144 | dist/
145 | downloads/
146 | eggs/
147 | .eggs/
148 | # lib/
149 | lib64/
150 | parts/
151 | sdist/
152 | var/
153 | wheels/
154 | pip-wheel-metadata/
155 | share/python-wheels/
156 | *.egg-info/
157 | .installed.cfg
158 | *.egg
159 | MANIFEST
160 |
161 | # LP files
162 | *.MPS
163 | *.mps
164 | *.LP
165 | *.lp
166 |
167 | models/*
168 | # Allow only epanet files and markdown files
169 | !models/*.md
170 | !models/*.inp
171 | !models/*.net
172 |
173 | # CCWI Case study outputs
174 | src/matlab_octave/case_studies/ccwi2023/figures/
175 | src/matlab_octave/case_studies/ccwi2023/outputs_*
176 |
--------------------------------------------------------------------------------
/GITHUB_COMMIT_GUIDE.md:
--------------------------------------------------------------------------------
1 | ---
2 | # Structure of a git commit message:
3 | [Source document: ](https://dev.to/wordssaysalot/art-of-writing-a-good-commit-message-56o7)
4 |
5 | > ### Commit message:
6 | > * type(scope): subject
7 | > * body (optional)
8 | > * footer (optional)
9 | ---
10 | ## Types of commits:
11 | * **feat** - a new feature
12 | * **fix** - a bug fix
13 | * **docs** - changes in documentation
14 | * **style** - everything related to styling
15 | * **refactor** - code changes that neither fixes a bug or adds a feature
16 | * **test** - everything related to testing
17 | * **chore** - updating build tasks, package manager configs, etc
18 | ---
19 | ## Scope:
20 | A scope MUST consist of a noun describing a section of the codebase affected by the changes (or simply the epic name) surrounded by parenthesis.
21 |
22 | > e.g.\
23 | > feat(claims)\
24 | > fix(orders)
25 | ---
26 | ## Subject - MANDATORY:
27 | A short description of the changes made. It shouldn't be greater than **50 characters**, should begin with a capital letter and written in the imperative eg. Add instead of Added or Adds.
28 |
29 | > e.g.\
30 | > feat(claims): add claims detail page\
31 | > fix(orders): validation of custom specification
32 | ---
33 | ## Body - OPTIONAL
34 | The body is used to explain what changes you made and why you made them.
35 | - Separate the subject from the body with a blank line
36 | - Limit each line to **72 characters**
37 | - Do not assume the reviewer understands what the original problem was, ensure you add it
38 | - Do not think your code is self-explanatory
39 |
40 | > e.g.\
41 | > refactor!: drop support for Node 6 \
42 | > BREAKING CHANGE: refactor to use JavaScript features not available in Node 6.
43 | ---
44 | ## Footer - OPTIONAL
45 | The footer is also optional and mainly used when you are using an **issue tracker** to the issues (issue IDs) affected by the code changes or comment to another developers or testers.
46 |
47 | > e.g.\
48 | > Resolves: #123\
49 | > See also: #456, #789
50 |
51 | > fix(orders): correct minor typos in code\
52 | > See the issue for details on typos fixed.\
53 | > Reviewed-by: @John Doe\
54 | > Refs #133
55 | ---
56 | ## GOOD COMMIT MESSAGE
57 | Good commit messages serve at least three important purposes:
58 |
59 | * To speed up the reviewing process.
60 | * To help us write a good release note.
61 | * To help the future maintainers, say five years into the future, to find out why a particular change was made to the code or why a specific feature was added.
62 |
63 | Information in commit messages:
64 |
65 | * Describe why a change is being made.
66 | * How does it address the issue?
67 | * What effects does the patch have?
68 | * Do not assume the reviewer understands what the original problem was.
69 | * Do not assume the code is self-evident/self-documenting.
70 | * Read the commit message to see if it hints at improved code structure.
71 | * The first commit line is the most important.
72 | * Describe any limitations of the current code.
73 | * Do not include patch set-specific comments.
74 | ---
75 |
76 |
--------------------------------------------------------------------------------
/USEFUL_LINKS.md:
--------------------------------------------------------------------------------
1 | # Python modules for EPANET
2 | * [EPyT](https://github.com/KIOS-Research/EPyT)
3 |
4 | # EPANET TOOLKIT
5 | * [EPANET](https://github.com/OpenWaterAnalytics/epanet)
6 | * [EPANET TOOLKIT DOCS](https://lopez-ibanez.eu/doc/toolkit_help.pdf)
7 |
8 | # MILP Solvers
9 | * [COIN-OR Branch and Cut solver (CBC)](https://coin-or.github.io/Cbc/intro.html
10 | * [SCIP-Solving Constraint Integer Programs](https://www.scipopt.org)
11 | * [PySCIPOpt](https://scipopt.github.io/PySCIPOpt/docs/html/index.html)
12 | * [PySCIPOpt source code](https://github.com/scipopt/PySCIPOpt)
13 | * [GOOGLE OR-TOOLS](https://developers.google.com/optimization/mip/)
14 |
--------------------------------------------------------------------------------
/data/README.md:
--------------------------------------------------------------------------------
1 | This folder contains data files required to run the code.
2 |
--------------------------------------------------------------------------------
/img/milops-wdn-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomjanus/milp-scheduling/43cc1ce686b8f810ff5aabec2d8035711623e044/img/milops-wdn-logo.png
--------------------------------------------------------------------------------
/lib/README.md:
--------------------------------------------------------------------------------
1 | This folder contains external libraries, e.g. LP solvers, etc.
2 |
--------------------------------------------------------------------------------
/models/OneTankTwoPumps/One_Tank_Two_Pump_v1_OPT_Pattern_H_H.net:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomjanus/milp-scheduling/43cc1ce686b8f810ff5aabec2d8035711623e044/models/OneTankTwoPumps/One_Tank_Two_Pump_v1_OPT_Pattern_H_H.net
--------------------------------------------------------------------------------
/models/README.md:
--------------------------------------------------------------------------------
1 | This folder contains models, i,e. code that is run but is not part of the
2 | scheduler source code.
3 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/ccwi2023/find_ht_mae_outliers.m:
--------------------------------------------------------------------------------
1 | for i=1:numel(batch_outputs)
2 |
3 | tank_diameters(i) = batch_outputs{i}.inputs.tank_diameter;
4 | tank_elevations(i) = batch_outputs{i}.inputs.elevation;
5 | mean_demands(i) = batch_outputs{i}.inputs.mean_demand;
6 | final_level_diffs(i) = batch_outputs{i}.inputs.elevation + 2.5 - batch_outputs{i}.inputs.final_level;
7 | mae(i) = batch_outputs{i}.outputs.ht_mae;
8 | end
9 |
10 | ix = find(mae > 0.6);
11 | disp("Tank diameters: ")
12 | disp(tank_diameters(ix))
13 |
14 | disp("Tank elevations: ")
15 | disp(tank_elevations(ix))
16 |
17 | disp("Mean demands: ")
18 | disp(mean_demands(ix))
19 |
20 | disp("Final level diff")
21 | disp(final_level_diffs(ix))
22 |
23 | disp("MAES")
24 | disp(mae(ix))
25 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/ccwi2023/run_cplex.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 | import argparse
3 | import os
4 | import cplex
5 | import mip
6 | import scipy.io
7 |
8 | #MAT_FILE = pathlib.Path("../data/2p1t/2p1t_model.mat")
9 | MPS_FILE = pathlib.Path("../data/2p1t/2p1t.mps")
10 | CPLEX_RESULTS_FILE = pathlib.Path("../outputs/2p1t/x_optim_cplex.mat")
11 | # Parrarel (multihreaded) execution
12 | os_threads = os.cpu_count()
13 |
14 | def run_cplex(
15 | mps_file: pathlib.Path, cplex_result: pathlib.Path,
16 | mip_gap: float = 0.05, num_threads: int | None = os_threads,
17 | mps_lp_convert: bool = True, debug: bool = False) -> None:
18 | """Reads the milp model saved in the mps_file and saves results to
19 | a mat file given in cplex_resuts."""
20 |
21 | # Convert MPS file to LP file
22 | if mps_lp_convert:
23 | # convert mps to lp using the mip package
24 | instance = mip.Model()
25 | instance.read(mps_file.as_posix())
26 | lp_file = mps_file.with_suffix(".lp")
27 | instance.write(lp_file.as_posix())
28 |
29 | problem = cplex.Cplex()
30 | problem.parameters.threads.set(num_threads)
31 | # problem.parameters.benders.strategy = -1
32 | problem.read(mps_file.as_posix())
33 | problem.set_problem_type(cplex.Cplex.problem_type.MILP)
34 | problem.parameters.mip.tolerances.mipgap.set(mip_gap)
35 | start_time = problem.get_time()
36 | problem.solve()
37 | end_time = problem.get_time()
38 | x_optim = problem.solution.get_values()
39 | objective = problem.solution.get_objective_value()
40 | wallclock_time = end_time - start_time
41 | if debug:
42 | print(f"Solution status: {problem.solution.get_status_string()}")
43 | print(f"Objective value: {objective}")
44 | scipy.io.savemat(
45 | cplex_result, {"x": x_optim, "obj": objective, "time": wallclock_time})
46 |
47 | def cli() -> int:
48 | """ """
49 | parser = argparse.ArgumentParser(description="Run CPLEX with given arguments")
50 | parser.add_argument("mps_file", type=pathlib.Path, help="Path to the MPS file")
51 | parser.add_argument("cplex_result", type=pathlib.Path, help="Path to the CPLEX result file")
52 | parser.add_argument("--mip_gap", type=float, default=0.05,
53 | help="MIP gap tolerance")
54 | parser.add_argument("--num_threads", type=int, default=os.cpu_count(),
55 | help="Number of threads to use")
56 | parser.add_argument("--no_mps_lp_convert", dest="mps_lp_convert",
57 | action="store_false", help="Disable MPS to LP conversion")
58 | parser.add_argument("--no_debug", dest="debug", action="store_false",
59 | help="Disable debugging mode")
60 | args = parser.parse_args()
61 |
62 | run_cplex(
63 | args.mps_file,
64 | args.cplex_result,
65 | mip_gap=args.mip_gap,
66 | num_threads=args.num_threads,
67 | mps_lp_convert=args.mps_lp_convert,
68 | debug=args.debug)
69 |
70 | return 0
71 |
72 |
73 | if __name__ == "__main__":
74 | """ """
75 | cli()
76 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/ccwi2023/transform_2p1t_to_milp_ccwi2023.m:
--------------------------------------------------------------------------------
1 | function lin_model = transform_2p1t_to_milp_ccwi2023(...
2 | input, linprog, network, pump_groups, init_sim_output, ...
3 | save_to_mps, save_to_mat, var_names, forced_final_tank_level, ...
4 | mps_filename, mat_filename)
5 | % Process the 2p1t model and create a mixed integer linear programme
6 | % representation (lin_model). Save to mps file and/or mat file
7 | % when required (if save_to_mps and save_to_mat respectively, are set to tru)
8 | sparse_out = 1;
9 | % Find q_op and s_op at 12
10 | pump_speed_12 = input.init_schedule.S(12);
11 | pump_flow_12 = init_sim_output.q(2,12)/input.init_schedule.N(12);
12 | % Step 4 - Formulate linear program
13 | % Initialise continuous and binary variable structures
14 | vars = initialise_var_structure(network, linprog);
15 | % Set the vector of indices of binary variables in the vector of decision variables x
16 | intcon_vector = set_intcon_vector(vars);
17 | % Set the vectcor of objective function coefficients c
18 | c_vector = set_objective_vector(vars, network, input, linprog, true);
19 | % Get variable (box) constraints for the two pump one tank network
20 | constraints = set_constraints_2p1t(network);
21 | [lb_vector, ub_vector] = set_variable_bounds(vars, constraints, forced_final_tank_level);
22 | % Linearize the pipe and pump elements
23 | lin_pipes = linearize_pipes_2p1t(network, init_sim_output);
24 | lin_pumps = linearize_pumps_2p1t(pump_groups);
25 | % Linearize the power consumption model
26 | % lin_power = linearize_pump_power_2p1t(pump_groups, pump_flow_12, ...
27 | % pump_speed_12);
28 | % Linearize pump model with dummy constriants and domain vertices
29 | % (as not relevant for linearization)
30 | constraint_signs = {'>', '<', '<', '>'};
31 | domain_vertices = {[0,0], [0,0], [0,0], [0,0]}; % Dummy variable due to the fact that linearize_power_model requires (any) input but in this case the variable does not do anything
32 | lin_power = linearize_power_model(pump_groups(1).pump, pump_flow_12, ...
33 | pump_speed_12, domain_vertices, constraint_signs);
34 | % Set the inequality constraints A and b
35 | [A, b] = set_A_b_matrices(vars, linprog, lin_power, lin_pipes, ...
36 | lin_pumps, pump_groups, sparse_out);
37 | % Set the equality constraints Aeq and beq
38 | [Aeq, beq] = set_Aeq_beq_matrices(vars, network, input, lin_pipes,...
39 | sparse_out);
40 | % Convert all individual matrices and vectors to a linear model structure
41 | lin_model.c_vector = c_vector;
42 | lin_model.A = A;
43 | lin_model.b = b;
44 | lin_model.Aeq = Aeq;
45 | lin_model.beq = beq;
46 | lin_model.lb = lb_vector;
47 | lin_model.ub = ub_vector;
48 | lin_model.intcon = intcon_vector;
49 | % Save to mps standard file, if save_to_mps is True
50 | % TODO: Add 'EleNames', 'EqtNames' for inequality and equality constraint names, respectively
51 | if save_to_mps == true
52 | if var_names == true
53 | % Create a cell with Variable names
54 | for ix=1:length(c_vector)
55 | variable = map_lp_vector_index_to_var(vars, ix);
56 | var_name = variable.subfield_name;
57 | var_subscript = strjoin(string(variable.index), '_');
58 | var_with_subscript = strjoin([var_name, var_subscript], '');
59 | if ix == 1
60 | var_names = {var_with_subscript};
61 | else
62 | var_names(end+1) = {var_with_subscript};
63 | end
64 | end
65 | var_names = cellstr(var_names);
66 | mps_text = BuildMPS(A, b, Aeq, beq, c_vector, lb_vector, ...
67 | ub_vector, 'MILP_Scheduler_2p1t', 'I', intcon_vector,...
68 | 'VarNames', var_names);
69 | else
70 | mps_text = BuildMPS(A, b, Aeq, beq, c_vector, lb_vector, ...
71 | ub_vector, 'MILP_Scheduler_2p1t', 'I', intcon_vector);
72 | end
73 | OK = SaveMPS(mps_filename, mps_text);
74 | % '../python/data/2p1t/2p1t.mps'
75 | end
76 | % Save to a mat file if save_to_map is True
77 | if save_to_mat == true
78 | % '../python/data/2p1t/2p1t_model.mat'
79 | save(mat_filename, '-struct', 'lin_model');
80 | end
81 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/two_pump_one_tank/hydraulics_2p1t.m:
--------------------------------------------------------------------------------
1 | function y = hydraulics_2p1t(x,n,s,d,htt, input, network, pump)
2 | % Hydraulic function for the simple two-pump one-tank network
3 | %
4 | % Args:
5 | % x - vector representing flows in the elements (5 vars) and heads at
6 | % connection nodes (4 vars) at time step k.
7 | % n - number of working pumps at time step k
8 | % s - pump speed s_min <= s <= s_max at time step k
9 | % d - nodal demands at time step k
10 | % htt - tank head at the nex time step
11 | % input - input structure
12 | % network - network structure
13 | % pump - pump structure
14 | %
15 | % Returns:
16 | % y - the vector of mass balance equations at the connection nodes (4 eqs)
17 | % and equations of components (5 eqs).
18 | %
19 | qh=x(1:network.ne);
20 | hch=x(network.ne+1:network.ne+network.nc);
21 | hf(1)=input.hr;
22 | hf(2)=htt;
23 | y=zeros(network.ne+network.nc,1);
24 | %
25 | y(1:network.nc)=network.Lc*qh.';
26 | y(network.nc)=y(network.nc)-d(4);
27 | % Heads in pipes
28 | dH = zeros(network.ne,1)';
29 | for i = 1:length(network.pipe_indices)
30 | pipe_index = network.pipe_indices(i);
31 | dH(pipe_index) = network.Rs(i)*qh(pipe_index)*abs(qh(pipe_index));
32 | end
33 | % Heads in pumps
34 | for i = 1:length(network.pump_group_indices)
35 | pump_index = network.pump_group_indices(i);
36 | dH(pump_index) = pump_head(pump, qh(pump_index), n, s);
37 | end
38 | %
39 | y(network.nc+1:network.nc+network.ne)=...
40 | dH.'+ network.Lc.'*hch.'+ network.Lf.'*hf.';
41 | y(network.nc+2)=y(network.nc+2)-n^2*(hch(2)-hch(1))-(hch(2)-hch(1));
42 | %
43 | end
44 |
45 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/two_pump_one_tank/initialise_2p1t.m:
--------------------------------------------------------------------------------
1 | function [input,const,linprog,network,sim,pump_groups] = initialise_2p1t()
2 | %% Initialise data for the Simulator
3 | % Represents input data for simulating the network with the schematic
4 | % given below:
5 | % n - node; e - element, p - pump, r - reservoir, d - demand
6 | %
7 | % n5 (tank)
8 | % |
9 | % | (p4)
10 | % (e5)
11 | %(r) (p1) (p2) (p3) | (d)
12 | %n1 ------ n2 ----(e2)----n3---(e4)---n4---(e6)---n6
13 | % | | (p5)
14 | % nc1----(e3)----nc2
15 |
16 | % Initializes the following data structures
17 | % linprog
18 | % sim
19 | % network
20 | % input
21 | % constants
22 |
23 | % TODO: Remove redundancy in data - e.g remove tank_feed_pipe index as it is
24 | % alredy included in the tank structure and pump_group_index as it is already
25 | % included in the pump structure
26 |
27 | %% Define network characteristics required for defining the data structures
28 | elevations = [210, 210, 220, 220, 230, 210]; % node elevations
29 | hr = 210; % reservoir head, m
30 | nt = 1; % number of tanks
31 | nr = 1; % number of reservoirs
32 | % Initial tank heads
33 | init_tank_levels = [2.5];
34 | % -------------------------------
35 | % (4 pipes: n1-n2; n3-n4; n4-n5; n5-n6)
36 | L_pipe = [10, 1610, 61, 1610];
37 | D_pipe = [1, 0.355, 0.458, 0.254];
38 | % Calculate pipe areas
39 | A_pipe = pi*D_pipe.^2/4;
40 | % Darcy-Weisbach coefficients
41 | fDW = [0.015, 0.015, 0.015, 0.015];
42 | DEMAND_MULTIPLIER = 40;
43 |
44 | % CONSTANTS
45 | const.g = 9.81;
46 |
47 | %% SIMULATION SETTINGS
48 | sim.TIME_HORIZON = 24; % Schedule performed over 24 hours
49 | sim.delta_t = 1; % time step (hrs)
50 | sim.time = 1:1:sim.TIME_HORIZON;
51 |
52 | %% DISCRETIZATION SETTINGS
53 | linprog.NO_PIPE_SEGMENTS = 3;
54 | linprog.NO_PUMP_SEGMENTS = 4;
55 | linprog.NO_PRED_STEPS = 24; % Prediction horizon
56 | linprog.TIME_STEP = 1;
57 | linprog.Upower = 1000;
58 | linprog.Upump = 100;
59 |
60 | %% NETWORK STRUCTURE AND NETWORK COMPONENTS
61 | % 1 PUMPS
62 | %%% PUMP hydraulic
63 | % Currently only one type of pump is used. Later expand to multiple different
64 | % pumps
65 | pump1.A = -0.0045;
66 | pump1.B = 0;
67 | pump1.C = 45;
68 | % Pump energy consumption:
69 | % P(q,s) = ep * q^3 + fp * q^2 * s + gp * q * s^2 + hp * s^3
70 | % where n is the number of pumps switched on and operating in parallel and s
71 | % is the pump speed.
72 | pump1.ep = 0.0;
73 | pump1.fp = 0.0;
74 | pump1.gp = 0.2422;
75 | pump1.hp = 40;
76 | pump1.smin = 0.7;
77 | pump1.smax = 1.2;
78 | pump1.qint_smax = pump_intercept_flow(pump1, 1, pump1.smax); % Intercept flow at maximum pump speed
79 | pump1.max_eff_flow = 45;
80 | pump_groups(1).pump = pump1;
81 | pump_groups(1).npumps = 2;
82 | pump_groups(1).element_index = 2;
83 |
84 | % 2. TANKS
85 | tank1.elevation = elevations(5);
86 | tank1.diameter = 15;
87 | tank1.area = pi*tank1.diameter^2/4.0;
88 | tank1.x_min = 0.5;
89 | tank1.x_max = 3.5;
90 | tank1.init_level = nan;
91 | tank1.ht0 = nan;
92 | tank1.feed_pipe_index = 4;
93 | tanks(1) = tank1;
94 | % Set initial levels (initialize tanks)
95 | for i = 1:length(tanks)
96 | tank = tanks(i);
97 | init_level = init_tank_levels(i);
98 | if ((init_level < tank.x_min) || (init_level > tank.x_max))
99 | error('Initial tank level not within bounds');
100 | else
101 | tanks(i).init_level = init_level;
102 | tanks(i).ht0 = tank.elevation + init_level; % make sure that x_min <= init_level <= x_max
103 | end
104 | end
105 |
106 | % 3. NETWORK TOPOLOGY
107 | % node-element incidence matrix for the (calculated) connection nodes
108 | network.Lc = [1 -1 0 0 0;...
109 | 0 1 -1 0 0;...
110 | 0 0 1 -1 -1;...
111 | 0 0 0 0 1];
112 | % node-element incidence matrix for the fixed (non-calculated) connection
113 | % nodes
114 | network.Lf=[-1 0 0 0 0;...
115 | 0 0 0 1 0];
116 | % Combined incidence matrix for calculated and non-calculated connection nodes
117 | network.L = [network.Lc; network.Lf];
118 |
119 | % Construct the network structure
120 | network.pump_groups = pump_groups;
121 | network.tanks = tanks;
122 | % FUTURE VARIABLES
123 | network.ncheck = 0; % number of check valves
124 | network.nprv = 0; % number of PRVs
125 |
126 | network.npg = length(network.pump_groups); % number of pump groups
127 | network.nt = nt; % number of tanks
128 | network.nr = nr; % number of reservoirs
129 | network.nc = size(network.Lc,1); % number of calculated nodes
130 | network.nf = size(network.Lf,1); % number of fixed head nodes
131 | network.pipe_indices=[1;3;4;5]; % Pipe flow index for the simulator results
132 | network.pump_group_indices = [2];
133 | network.npipes = length(network.pipe_indices);
134 | % Number of elements is equal to number of pipes + number of pump groups
135 | network.ne = network.npipes + network.npg;
136 |
137 | network.elements.tank_feed_pipe_index = 4;
138 |
139 | % Find the total number of pumps
140 | npumps = 0;
141 | for i = 1:length(network.pump_groups)
142 | npumps = npumps + network.pump_groups(i).npumps;
143 | end
144 | network.npumps = npumps;
145 |
146 | % Indices of calculated nodes, reexservoir nodes and tank nodes, respectively
147 | network.hc_indices=[1;2;3;4]; % Row number in network.L matrix
148 | network.hr_indices=[5]; % Row number in network.L matrix
149 | network.ht_indices=[6]; % Row number in network.L matrix
150 |
151 | % Pipe resistances
152 | network.Rs = 10^-6*fDW.*L_pipe./(2*const.g*D_pipe.*A_pipe.^2);
153 |
154 | %% INPUTS
155 | % A 24x1 vector of tariffs
156 | input.tariff = [0.072490, 0.072490, 0.072490, 0.072490, 0.072490, 0.072490, ...
157 | 0.072490, 0.087490, 0.087490, 0.087490, 0.087490, 0.087490, 0.087490, ...
158 | 0.087490, 0.087490, 0.15195, 0.15195, 0.15195, 0.084340, 0.084340, ...
159 | 0.084340, 0.084340, 0.084340, 0.084340];
160 | %% Initial pump schedules
161 | input.init_schedule.N = [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1];
162 | input.init_schedule.S = 0.85 * ones(1, sim.TIME_HORIZON);
163 | % A 24x1 vector of demands for calculated node 4
164 | demand4 = [0.72, 0.59, 0.53, 0.47, 0.51, 0.55, 1.1, 1.53, 1.51, 1.34, 1.3, ...
165 | 1.3, 1.24, 1.24, 1.17, 1.14, 1.06, 1.121, 1.15, 1.31, 1.24, 1.21, ...
166 | 1.13, 1.14];
167 | % Create a matrix of nodal demands representative of the whole network (to
168 | % be used by the MILP formulation
169 | input.demands = zeros(network.nc, sim.TIME_HORIZON);
170 | input.demands(4,:) = demand4;
171 | input.demands = input.demands * DEMAND_MULTIPLIER;
172 | input.demands = sparse(input.demands);
173 | input.df = 1;
174 | input.hr = hr;
175 |
176 | end
177 |
178 | % Differentiate between element and pipe/pump indices.
179 | % In the simulator, all elements are lumped into a signle vector but, for the
180 | % purpose of MILP, pumps and pipes are treated separately
181 | %network.pipes.tank_feed_pipe_index = 3;
182 | %network.elements.tank_feed_pipe_index = 4;
183 | %network.elements.pump_index=[2;3];
184 |
185 |
186 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/two_pump_one_tank/linearize_pipes_2p1t.m:
--------------------------------------------------------------------------------
1 | function out = linearize_pipes_2p1t(network, sim_out)
2 | % Wrapper for linearize_pipes configured for linearizing the 2pt1
3 | % case study model.
4 | rep_hr = 12;
5 | Upipes = [150, 150, 150, 150];
6 | out = linearize_pipes(network, sim_out, rep_hr, Upipes);
7 | end
8 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/two_pump_one_tank/linearize_pump_power_2p1t.m:
--------------------------------------------------------------------------------
1 | function out = linearize_pump_power_2p1t(pump_groups, q_op, s_op)
2 | % Wrapper for linearize_pumps configured for linearizing the 2pt1
3 | % case study model.
4 | % Currently, just callse the linearize_pumps model but it's
5 | % provided for consistency with linearize_pipes function.
6 | out = linearize_power_pumps(pump_groups, q_op, s_op);
7 | end
8 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/two_pump_one_tank/linearize_pumps_2p1t.m:
--------------------------------------------------------------------------------
1 | function out = linearize_pumps_2p1t(pump_groups)
2 | % Wrapper for linearize_pumps configured for linearizing the 2pt1
3 | % case study model.
4 | % Currently, just callse the linearize_pumps model but it's
5 | % provided for consistency with linearize_pipes function.
6 | out = linearize_pumps(pump_groups);
7 | end
8 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/two_pump_one_tank/linprog_to_sim_schedule.m:
--------------------------------------------------------------------------------
1 | function [sim_n, sim_s] = linprog_to_sim_schedule(optim_n, optim_s)
2 | % Conversion of pump schedule obtained from the mixed integer
3 | % linear programme to pump schedule compatible with the simulator
4 |
5 | % Currently, only applies to cases with a single pump group
6 |
7 | % This only works for a single pump group
8 | sim_n = sum(optim_n,2)';
9 | sim_s = zeros(size(optim_s, 1), 1);
10 | for i = 1:size(optim_s,1)
11 | s_row = optim_s(i,:);
12 | % If pumps are all ON, use mean speed
13 | if all(s_row)
14 | sim_s(i) = mean(s_row);
15 | continue
16 | end
17 | % If all pumps are OFF, set speed to zero
18 | if ~any(s_row)
19 | sim_s(i) = 0;
20 | continue
21 | end
22 | % If some of the pumps are off, select ones that are not OFF and
23 | % take the average
24 | sim_s(i) = mean(s_row(s_row~=0));
25 | end
26 | sim_s = sim_s';
27 | end
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/two_pump_one_tank/plot_2p1t_simulation_results.m:
--------------------------------------------------------------------------------
1 | function y = plot_2p1t_simulation_results(output, N, S, input, sim, titles, save_to_pdf, folder)
2 |
3 | print_plots = [1, 1, 1, 1];
4 | y = 0;
5 | TIME_HORIZON = 24;
6 |
7 | % Plot element flows and demand
8 | if (print_plots(1) == 1)
9 | file_name1 = fullfile(folder, 'flows_n_demand_initial');
10 | q3 = output.q(3,:);
11 | q4 = output.q(4,:);
12 | q5 = output.q(5,:);
13 | plot_elem_flows_demands_2p1t(TIME_HORIZON, input, q3, q4, q5, file_name1, titles{1}, save_to_pdf);
14 | end
15 | % Plot heads at nodes
16 | if (print_plots(2) == 1)
17 | file_name2 = fullfile(folder, 'heads_initial');
18 | hc1 = output.hc(1,:);
19 | hc2 = output.hc(2,:);
20 | hc3 = output.hc(3,:);
21 | hc4 = output.hc(4,:);
22 | ht = output.ht(1:end-1);
23 | plot_nodal_heads_2p1t(TIME_HORIZON, hc1, hc2, hc3, hc4, ht, file_name2, titles{2}, save_to_pdf);
24 | end
25 | % Plot energy consumption
26 | if (print_plots(3) == 1)
27 | pump_cost = output.f;
28 | file_name3 = fullfile(folder, 'energy_consumption');
29 | plot_energy_consumption_2p1t(TIME_HORIZON, input, pump_cost, file_name3, titles{3}, save_to_pdf);
30 | end
31 | % Plot pump schedules
32 | if (print_plots(4) == 1)
33 | file_name4 = fullfile(folder, 'schedule');
34 | plot_pump_schedules_2p1t(TIME_HORIZON, N, S, file_name4, titles{4}, save_to_pdf);
35 | end
36 | y = 1;
37 | end
38 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/two_pump_one_tank/plot_elem_flows_demands_2p1t.m:
--------------------------------------------------------------------------------
1 | function y = plot_elem_flows_demands_2p1t(time_horizon, input, q3, q4, q5, file_name, plot_title, save_to_pdf)
2 | % Plot element flows from the two pump one tank system
3 | AXIS_FONTSIZE = 18;
4 | ANNOTATION_FONTSIZE = 20;
5 | LABEL_FONTSIZE = 21;
6 | TITLE_FONTSIZE = 24;
7 | LEGEND_FONTSIZE = 18;
8 | y = 0;
9 | % Plot element flows and demand
10 | hf1=figure();
11 | hold on;
12 | % Add padding
13 | q3 = [q3(1:time_horizon) q3(time_horizon)];
14 | q4 = [q4(1:time_horizon) q4(time_horizon)];
15 | q5 = [q5(1:time_horizon) q5(time_horizon)];
16 | time_1=[0:time_horizon];
17 | stairs(time_1,q3,'LineWidth',2);
18 | stairs(time_1,q4,'LineWidth',2);
19 | stairs(time_1, q5,'LineWidth',2);
20 | % Plot demand
21 | demand=[input.demands(4,1:time_horizon)*input.df, input.demands(4, time_horizon)*input.df];
22 | demand_point = stairs(time_1, demand,'o');
23 | set(demand_point, ...
24 | 'Marker', 'o', ...
25 | 'MarkerSize', 8, ...
26 | 'MarkerFaceColor', [1 1 1]);
27 | xticks(0:1:time_horizon);
28 | title(plot_title, 'fontsize', TITLE_FONTSIZE,...
29 | 'interpreter', 'latex');
30 | ll = legend('$q_3$ - pump','$q_4$ - tank feed','$q_5$ - demand supply', ...
31 | 'forced demand');
32 | set(ll,...
33 | 'fontsize', LEGEND_FONTSIZE,...
34 | 'interpreter', 'latex',...
35 | 'color', 'none',...
36 | 'box', 'off',...
37 | 'Location','southwest');
38 | xlabel ('Time, hrs', 'fontsize', LABEL_FONTSIZE, 'interpreter', 'latex');
39 | ylabel ('Flow, L/s', 'fontsize', LABEL_FONTSIZE,...
40 | 'interpreter', 'latex');
41 | set(gca,'fontsize', AXIS_FONTSIZE)
42 | xlim([0 time_horizon])
43 | ylim([-80 100])
44 | xticks([0:2:time_horizon])
45 | grid on;
46 | hold off;
47 | if (save_to_pdf == 1) && (get_system() == "OCTAVE")
48 | print(hf1, file_name, '-dpdflatexstandalone');
49 | pdflatex_command = sprintf('pdflatex %s', file_name);
50 | system(pdflatex_command);
51 | end
52 | y = 1;
53 | end
54 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/two_pump_one_tank/plot_energy_consumption_2p1t.m:
--------------------------------------------------------------------------------
1 | function y = plot_energy_consumption_2p1t(time_horizon, input, pump_power, file_name, plot_title, save_to_pdf)
2 | % Plot energy consumption due to pumping in the 2 pump 1 tank network.
3 | AXIS_FONTSIZE = 18;
4 | ANNOTATION_FONTSIZE = 20;
5 | LABEL_FONTSIZE = 21;
6 | TITLE_FONTSIZE = 24;
7 | LEGEND_FONTSIZE = 16;
8 | y = 0;
9 | hf3 = figure();
10 | xticks(0:1:time_horizon);
11 | xlabel ('Time, hrs', 'fontsize', LABEL_FONTSIZE, 'interpreter', 'latex');
12 | yyaxis left
13 | pump_power=[pump_power(1:time_horizon); pump_power(time_horizon)];
14 | time_1=[0:time_horizon];
15 | stairs(time_1, pump_power,'LineWidth',2);
16 | ylabel('Pumping cost [GBP/hr]', 'fontsize', LABEL_FONTSIZE, ...
17 | 'interpreter', 'latex');
18 | hold on;
19 | ylim([0 10])
20 | yyaxis right
21 | tariff=[input.tariff(1:time_horizon) input.tariff(time_horizon)];
22 | stairs(time_1, tariff * 1000,'LineWidth',2);
23 | ylabel('Energy tariff [GBP/MWh]', 'fontsize', LABEL_FONTSIZE, ...
24 | 'interpreter', 'latex');
25 | title(plot_title, 'fontsize', TITLE_FONTSIZE,...
26 | 'interpreter', 'latex');
27 | ll=legend('energy cost','tariff');
28 | set(ll,...
29 | 'fontsize', LEGEND_FONTSIZE,...
30 | 'interpreter', 'latex',...
31 | 'color', 'none',...
32 | 'box', 'off',...
33 | 'Location','southwest');
34 | set(gca,'fontsize', AXIS_FONTSIZE)
35 | xlim([0 time_horizon])
36 | xticks([0:2:time_horizon])
37 | ylim([60 160])
38 | grid;
39 | hold off;
40 | if (save_to_pdf == 1) && (get_system() == "OCTAVE")
41 | print(hf3, file_name, '-dpdflatexstandalone');
42 | pdflatex_command = sprintf('pdflatex %s', file_name);
43 | system(pdflatex_command);
44 | end
45 | y = 1;
46 | end
47 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/two_pump_one_tank/plot_linprog_pump_schedules_2p1t.m:
--------------------------------------------------------------------------------
1 | function y = plot_linprog_pump_schedules_2p1t(...
2 | time_horizon, n_pumps, s_pumps, file_name, plot_title, save_to_pdf)
3 | % Plot pump schedules from the results of the 2 pump 1 tank model
4 | AXIS_FONTSIZE = 18;
5 | ANNOTATION_FONTSIZE = 20;
6 | LABEL_FONTSIZE = 21;
7 | TITLE_FONTSIZE = 24;
8 | LEGEND_FONTSIZE = 16;
9 |
10 | hf4 = figure();
11 | time_1=[0:time_horizon];
12 | n_pumps=[n_pumps(1:time_horizon) n_pumps(time_horizon)];
13 | xticks(0:1:time_horizon);
14 | xlabel ('Time, hrs', 'fontsize', LABEL_FONTSIZE, 'interpreter', 'latex');
15 | yyaxis left
16 | stairs(time_1,n_pumps,'LineWidth',2);
17 | ylabel('No. of working pumps', 'fontsize', LABEL_FONTSIZE, ...
18 | 'interpreter', 'latex');
19 | ylim([0 2])
20 | hold on;
21 | yyaxis right
22 | %s_pumps_1=[s_pumps((1:time_horizon),1) s_pumps(time_horizon,1)];
23 | %s_pumps_1=[s_pumps((1:time_horizon),2) s_pumps(time_horizon,2)];
24 | s_pumps_1 = [s_pumps(:,1);1]';
25 | s_pumps_2 = [s_pumps(:,2);1]';
26 | stairs(time_1,s_pumps_1,'LineWidth',2);
27 | stairs(time_1,s_pumps_2,'LineWidth',2);
28 | ylabel('Pump speed (-)', 'fontsize', LABEL_FONTSIZE, ...
29 | 'interpreter', 'latex');
30 | title(plot_title, 'fontsize', TITLE_FONTSIZE,...
31 | 'interpreter', 'latex');
32 | ll = legend('No. working pumps','Pump 1 speed (relative to nominal)','Pump 2 speed (relative to nominal)');
33 | set(ll,...
34 | 'fontsize', LEGEND_FONTSIZE,...
35 | 'interpreter', 'latex',...
36 | 'color', 'none',...
37 | 'box', 'off',...
38 | 'Location','southwest');
39 | set(gca,'fontsize', AXIS_FONTSIZE);
40 | ylim([0 2])
41 | xlim([0 time_horizon])
42 | xticks([0:2:time_horizon]);
43 | grid;
44 | hold off;
45 | if (save_to_pdf == 1) && (get_system() == "OCTAVE")
46 | print(hf4, file_name, '-dpdflatexstandalone');
47 | pdflatex_command = sprintf('pdflatex %s', file_name);
48 | system(pdflatex_command);
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/two_pump_one_tank/plot_nodal_heads_2p1t.m:
--------------------------------------------------------------------------------
1 | function y = plot_nodal_heads_2p1t(time_horizon, hc1, hc2, hc3, hc4, ht, file_name, plot_title, save_to_pdf)
2 | % Plot heads in calculated nodes in the two pump one tank system
3 | AXIS_FONTSIZE = 18;
4 | ANNOTATION_FONTSIZE = 20;
5 | LABEL_FONTSIZE = 21;
6 | TITLE_FONTSIZE = 24;
7 | LEGEND_FONTSIZE = 18;
8 | y = 0;
9 | hf2 = figure();
10 | hold on;
11 | hc1=[hc1(1:time_horizon) hc1(time_horizon)];
12 | hc2=[hc2(1:time_horizon) hc2(time_horizon)];
13 | hc3=[hc3(1:time_horizon) hc3(time_horizon)];
14 | hc4=[hc4(1:time_horizon) hc4(time_horizon)];
15 | ht=[ht(1:time_horizon) ht(time_horizon)];
16 | time_1=[0:time_horizon];
17 | stairs(time_1, hc1, 'LineWidth',2);
18 | stairs(time_1, hc2, 'LineWidth',2);
19 | stairs(time_1, hc3, 'LineWidth',2);
20 | stairs(time_1, hc4, 'LineWidth',2);
21 | plot(time_1, ht, 'k-*');
22 | xticks(0:1:time_horizon);
23 | xlabel ('Time, hrs', 'fontsize', LABEL_FONTSIZE, 'interpreter', 'latex');
24 | ylabel('Head, m', 'fontsize', LABEL_FONTSIZE, 'interpreter', 'latex');
25 | title(plot_title, 'fontsize', TITLE_FONTSIZE,...
26 | 'interpreter', 'latex');
27 | ll = legend('$h_2$ - pump inlet','$h_3$ - pump outlet',...
28 | '$h_4$ - node 4','$h_6$ - demand node', '$h_t$ - tank level');
29 | set(ll,...
30 | 'fontsize', LEGEND_FONTSIZE,...
31 | 'interpreter', 'latex',...
32 | 'color', 'none',...
33 | 'box', 'off',...
34 | 'Location','southwest');
35 | set(gca,'fontsize', AXIS_FONTSIZE)
36 | xlim([0 time_horizon])
37 | ylim([210 240])
38 | xticks([0:2:time_horizon])
39 | grid on;
40 | hold off;
41 | if (save_to_pdf == 1) && (get_system() == "OCTAVE")
42 | print(hf2, file_name, '-dpdflatexstandalone');
43 | pdflatex_command = sprintf('pdflatex %s', file_name);
44 | system(pdflatex_command);
45 | end
46 | y = 1;
47 | end
48 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/two_pump_one_tank/plot_pump_schedules_2p1t.m:
--------------------------------------------------------------------------------
1 | function y = plot_pump_schedules_2p1t(...
2 | time_horizon, n_pumps, s_pumps, file_name, plot_title, save_to_pdf)
3 | % Plot pump schedules from the results of the 2 pump 1 tank model
4 | AXIS_FONTSIZE = 18;
5 | ANNOTATION_FONTSIZE = 20;
6 | LABEL_FONTSIZE = 21;
7 | TITLE_FONTSIZE = 24;
8 | LEGEND_FONTSIZE = 16;
9 |
10 | hf4 = figure();
11 | time_1=[0:time_horizon];
12 | n_pumps=[n_pumps(1:time_horizon) n_pumps(time_horizon)];
13 | xticks(0:1:time_horizon);
14 | xlabel ('Time, hrs', 'fontsize', LABEL_FONTSIZE, 'interpreter', 'latex');
15 | yyaxis left
16 | stairs(time_1,n_pumps,'LineWidth',2);
17 | ylabel('No. of working pumps', 'fontsize', LABEL_FONTSIZE, ...
18 | 'interpreter', 'latex');
19 | ylim([0 2])
20 | hold on;
21 | yyaxis right
22 | s_pumps=[s_pumps(1:time_horizon) s_pumps(time_horizon)];
23 | stairs(time_1,s_pumps,'LineWidth',2);
24 | ylabel('Pump speed (-)', 'fontsize', LABEL_FONTSIZE, ...
25 | 'interpreter', 'latex');
26 | title(plot_title, 'fontsize', TITLE_FONTSIZE,...
27 | 'interpreter', 'latex');
28 | ll = legend('No. working pumps','Pump speed (relative to nominal)');
29 | set(ll,...
30 | 'fontsize', LEGEND_FONTSIZE,...
31 | 'interpreter', 'latex',...
32 | 'color', 'none',...
33 | 'box', 'off',...
34 | 'Location','southwest');
35 | set(gca,'fontsize', AXIS_FONTSIZE);
36 | ylim([0 2])
37 | xlim([0 time_horizon])
38 | xticks([0:2:time_horizon]);
39 | grid;
40 | hold off;
41 | if (save_to_pdf == 1) && (get_system() == "OCTAVE")
42 | print(hf4, file_name, '-dpdflatexstandalone');
43 | pdflatex_command = sprintf('pdflatex %s', file_name);
44 | system(pdflatex_command);
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/two_pump_one_tank/plot_scheduling_2p1t_results.m:
--------------------------------------------------------------------------------
1 | function y = plot_scheduling_2p1t_results(init_sim_output, optim_output, ...
2 | optim_sim_output, input, sim, vars, folder)
3 | % Plot the results of the optimal pump scheduling exercise on a two
4 | % pump one tank system.
5 | % Args:
6 | % init_sim_output - output from the simulation with initial schedules
7 | % optim_output - output from the MILP model
8 | % optim_sim_output - output from the simulation with optimized schedules
9 | % input - input structure (shared between all models)
10 | % sim - sim structure with simulation configuration parameters
11 | % vars - variables structure
12 | % folder -
13 | y = 0;
14 | initial_simulation_titles = {...
15 | 'Flows in selected elements - init sim', ...
16 | 'Heads at selected nodes - init sim', ...
17 | 'Energy cost and tariff - init sim', ...
18 | 'Pump schedule - init sim'};
19 | lin_model_titles = {...
20 | 'Flows in selected elements - MILP', ...
21 | 'Heads at selected nodes - MILP', ...
22 | 'Energy cost and tariff - MILP', ...
23 | 'Pump schedule - MILP'};
24 | final_simulation_titles = {...
25 | 'Flows in selected elements - final sim', ...
26 | 'Heads at selected nodes - final sim', ...
27 | 'Energy cost and tariff - final sim', ...
28 | 'Pump schedule - final sim'};
29 | % Plot initial simulation results
30 | plot_2p1t_simulation_results(init_sim_output, ...
31 | input.init_schedule.N, input.init_schedule.S, input, sim, ...
32 | initial_simulation_titles, 1, folder);
33 | % Plot optimization results
34 | plot_optim_outputs(...
35 | input, optim_output.x, vars, optim_output, lin_model_titles, folder);
36 | % Plot results of simulation with optimized pump schedules
37 | plot_2p1t_simulation_results(optim_sim_output, ...
38 | optim_output.schedule.N, optim_output.schedule.S, input, ...
39 | sim, final_simulation_titles, 1, folder);
40 | y = 1;
41 | end
42 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/two_pump_one_tank/set_constraints_2p1t.m:
--------------------------------------------------------------------------------
1 | function y = set_constraints_2p1t(network)
2 | % Create a constraint structure for the two pump one tank system
3 | % The returned structure must match the fields of the variable
4 | % structure used to formulate the MILP problem.
5 |
6 | % Note. Single lower and upper bound is currently supported for each
7 | % vector. Later, this and lb and ub boundary setting function should be
8 | % extended to allow different boundaries for different network components
9 | % e.g. for different tanks or pumps.
10 |
11 | x_cont = struct();
12 | x_bin = struct();
13 | % Get tank from the network structure - there's only one tank in this case study
14 | tank = network.tanks(1);
15 |
16 | x_cont.ht = [tank.elevation+tank.x_min, tank.elevation+tank.x_max];
17 | x_cont.hc = [205, 240];
18 | x_cont.qel = [-150, 150];
19 | x_cont.ww = [-150, 150];
20 | x_cont.ss = [0, 1.2];
21 | x_cont.qq = [0, 120];
22 | x_cont.q = [0, 120];
23 | x_cont.p = [0, 100];
24 | x_cont.s = [0.0, 1.2];
25 |
26 | x_bin.bb = [0, 1];
27 | x_bin.aa = [0, 1];
28 | x_bin.n = [0, 1]; % Each pump treated individually. Think later about changing
29 | % n value constraints so that it can also accept values larger than one and
30 | % only consider one (representative) pump in a group instead of every pump
31 | % separately
32 |
33 | y.x_cont = x_cont;
34 | y.x_bin = x_bin;
35 | end
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/matlab_octave/case_studies/two_pump_one_tank/simulator_2p1t.m:
--------------------------------------------------------------------------------
1 | function output = simulator_2p1t(schedule, network, input, sim)
2 | % Simulate a simple two-pumps one-tank network
3 | % Args:
4 | % schedule (struct): pump control schedule N: (0/1), pump speed schedule, (0-1)
5 | % network (struct): network parameters
6 | % input (struct): input data
7 | % sim: (struct): simulation parameters
8 |
9 | % Returns:
10 | % output struct with the following fields:
11 | % q: vector of flows in each pipe for time steps 1 to time horizon, L/S
12 | % hc: vector of calculated heads for time steps 1 to time horizon, m
13 | % P: vector of power consumption for time steps 1 to time horizon, ??
14 | % f: vector of energy cost for time steps 1 to time_horizon.
15 | % ht: vector of tank heads for time steps 1 to time horizon, m
16 | % h: vector of all nodal haeds for time step 1 to time horizon, m
17 |
18 | if nargin == 7
19 | time_step = 1; % Add default time step of 1hr if time step not provided
20 | else
21 | time_step = sim.delta_t;
22 | end
23 |
24 | tank = network.tanks(1);
25 | pump = network.pump_groups(1).pump;
26 |
27 | ht(1)=tank.ht0;
28 | htt=ht(1);
29 | % q1 q2 q3 q4 q5 h2 h3 h4 h5
30 | x0=[0 0 0 30 -30 210 210 232 232];
31 | P=zeros(sim.TIME_HORIZON,1); % Initialize the power consumption vector
32 | f=zeros(size(P)); % Initialize the cost vector
33 |
34 | % Run extended period simulation for the period of K steps
35 | for k=1:sim.TIME_HORIZON
36 | n=schedule.N(k); % Get number of working pumps at time step k
37 | s=schedule.S(k); % Get pump speed at time step k
38 | dk=input.df*input.demands(:,k); % Get scaled demand at time step k
39 | % Solve the network equations for time-step k
40 | % hydraulics_2p1t(x0, n, s, dk, htt, input, network, pump)
41 | x=fsolve(@(x)hydraulics_2p1t(x, n, s, dk, htt, input, network, pump), x0);
42 | % Retrieve vector of pipe flows from solution x
43 | q(:,k)=x(1:network.ne);
44 | % Retrieve vector of calculated heads from solution x
45 | hc(:,k)=x(network.ne+1 : network.ne+network.nc);
46 | % Get pump consumption from the pump group flow and speed and number of
47 | % operating pumps at time step k
48 | P(k) = pump_power_consumption(pump, q(network.pump_group_indices(1),k), ...
49 | s, n);
50 | f(k) = P(k) * input.tariff(k);
51 | % Advance state in time
52 | % Calculate water of volume passing through the tank feed pipe
53 | dV = q(4,k)*time_step;
54 | ht(k+1)=ht(k)+vol_to_h_constant_area(tank.area, dV);
55 | % Update the h vector
56 | h(:,k)=[hc(:,k);input.hr;ht(k)];
57 | % Tank water head for the next iteration is the projected tank water head
58 | htt=ht(k+1);
59 | end
60 | output.q = q;
61 | output.h = h;
62 | output.P = P;
63 | output.f = f;
64 | output.hc = hc;
65 | output.ht = ht;
66 |
67 | end
68 |
--------------------------------------------------------------------------------
/src/matlab_octave/external/BuildMPS/README.txt:
--------------------------------------------------------------------------------
1 | Build MPS matrix string that contains linear programming problem:
2 |
3 | Minimizing (for x in R^n): f(x) = cost'*x, subject to
4 | A*x <= b (LE)
5 | Aeq*x = beq (EQ)
6 | L <= x <= U (BD).
7 |
8 | Only single rhs (b and beq) is supported.
9 |
10 | Also supported is integer/mixte programming problem similar to the above,
11 | where a subset of components of x is restricted to be integer (N set) or
12 | binary set {0,1}.
13 |
14 | The MPS file format was introduced for an IBM program, but has also been
15 | accepted by most subsequent linear programming codes.
16 |
17 | To learn about mps format, please see:
18 | http://lpsolve.sourceforge.net/5.5/mps-format.htm
19 | http://www.mosek.com/fileadmin/products/6_0/tools/doc/html/tools/node018.html
20 |
21 |
22 | Usage example:
23 | A = [1 1 0; -1 0 -1];
24 | b = [5; -10];
25 | L = [0; -1; 0];
26 | U = [4; +1; +inf];
27 | Aeq = [0 -1 1];
28 | beq = 7;
29 | cost = [1 4 9];
30 | VarNameFun = @(m) (char('x'+(m-1))); % returning varname 'x', 'y' 'z'
31 |
32 | Qle = [2 1 0;
33 | 1 2 0;
34 | 0 0 1];
35 | g = [0; 0; -3];
36 | bquad = 100;
37 | quad_le = struct('Q', Qle, ...
38 | 'g', g, ...
39 | 'bquad', bquad);
40 |
41 | Qcost = speye(3);
42 | quad_cost = struct('Q', Qcost, ...
43 | 'name', 'cost'),
44 | Contain = BuildMPS(A, b, Aeq, beq, cost, L, U, 'Pbtest', ...
45 | 'VarNameFun', VarNameFun, ...
46 | 'EqtNames', {'Equality'}, ...
47 | 'Q', quad_le, 'Q', quad_cost, ...
48 | 'Integer', [1], ... % first variable 'x' integer
49 | 'MPSfilename', 'Pbtest.mps');
50 |
51 | Author: Bruno Luong
52 | update: 15-Jul-2008: sligly improved number formatting
53 | 25-Aug-2009: Improvement in handling sparse matrix
54 | 03-Sep-2009: integer/binary variables
55 | 02-May-2010: quadratic term
--------------------------------------------------------------------------------
/src/matlab_octave/external/BuildMPS/SaveMPS.m:
--------------------------------------------------------------------------------
1 | function OK=SaveMPS(filename, Contain)
2 | % function OK=SaveMPS(filename, Contain);
3 | %
4 | % Save matrix sring Contain in file "filename"
5 | % Return OK == 1 if saving is success
6 | % OK == 0 otherwise
7 | %
8 | % See also: BuildMPS
9 | %
10 | % Author: Bruno Luong
11 | % Last update: 18/April/2008
12 |
13 | % Default value of something goes wrong
14 | OK=0;
15 |
16 | % Open the file
17 | fid=fopen(filename,'w');
18 | if fid==-1
19 | return
20 | end
21 |
22 | % Write each line
23 | for n=1:size(Contain,1)
24 | fprintf(fid,'%s\n', Contain(n,:));
25 | end
26 |
27 | % Close and exit
28 | fclose(fid);
29 | OK=1;
30 |
31 |
--------------------------------------------------------------------------------
/src/matlab_octave/external/BuildMPS/license.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009, Bruno Luong
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are
6 | met:
7 |
8 | * Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | * Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in
12 | the documentation and/or other materials provided with the distribution
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 | POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/src/matlab_octave/external/saveJSONfile/license.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, Lior Kirsch
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are
6 | met:
7 |
8 | * Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | * Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in
12 | the documentation and/or other materials provided with the distribution
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 | POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/src/matlab_octave/external/saveJSONfile/saveJSONfile.m:
--------------------------------------------------------------------------------
1 | function saveJSONfile(data, jsonFileName)
2 | % saves the values in the structure 'data' to a file in JSON format.
3 | %
4 | % Example:
5 | % data.name = 'chair';
6 | % data.color = 'pink';
7 | % data.metrics.height = 0.3;
8 | % data.metrics.width = 1.3;
9 | % saveJSONfile(data, 'out.json');
10 | %
11 | % Output 'out.json':
12 | % {
13 | % "name" : "chair",
14 | % "color" : "pink",
15 | % "metrics" : {
16 | % "height" : 0.3,
17 | % "width" : 1.3
18 | % }
19 | % }
20 | %
21 | fid = fopen(jsonFileName,'w');
22 | %fid=1;
23 | writeElement(fid, data,'');
24 | fprintf(fid,'\n');
25 | fclose(fid);
26 | end
27 |
28 | % function buildJSON(fid,data)
29 | % namesOfFields = fieldnames(data);
30 | % numFields = length(namesOfFields);
31 | %
32 | % if isstruct(
33 | % fprintf(fid,'{ "%s" : [\n ',rootElementName);
34 | % for m = 1:length(data) - 1
35 | % fprintf(fid,'{ \n ');
36 | % writeSingleGene(fid,numFields, namesOfFields,dataname,data,m);
37 | % fprintf(fid,'},\n');
38 | % end
39 | % m= m+1;
40 | % fprintf(fid,'{ \n ');
41 | % writeSingleGene(fid, numFields, namesOfFields,dataname,data,m);
42 | % fprintf(fid,' }\n]\n');
43 | %
44 | %
45 | % end
46 |
47 | function writeElement(fid, data,tabs)
48 | namesOfFields = fieldnames(data);
49 | numFields = length(namesOfFields);
50 | tabs = sprintf('%s\t',tabs);
51 | fprintf(fid,'{\n%s',tabs);
52 |
53 |
54 | for i = 1:numFields - 1
55 | currentField = namesOfFields{i};
56 | currentElementValue = eval(sprintf('data.%s',currentField));
57 | writeSingleElement(fid, currentField,currentElementValue,tabs);
58 | fprintf(fid,',\n%s',tabs);
59 | end
60 | if isempty(i)
61 | i=1;
62 | else
63 | i=i+1;
64 | end
65 |
66 |
67 | currentField = namesOfFields{i};
68 | currentElementValue = eval(sprintf('data.%s',currentField));
69 | writeSingleElement(fid, currentField,currentElementValue,tabs);
70 | fprintf(fid,'\n%s}',tabs);
71 | end
72 |
73 | function writeSingleElement(fid, currentField,currentElementValue,tabs)
74 |
75 | % if this is an array and not a string then iterate on every
76 | % element, if this is a single element write it
77 | if length(currentElementValue) > 1 && ~ischar(currentElementValue)
78 | fprintf(fid,' "%s" : [\n%s',currentField,tabs);
79 | for m = 1:length(currentElementValue)-1
80 | writeElement(fid, currentElementValue(m),tabs);
81 | fprintf(fid,',\n%s',tabs);
82 | end
83 | if isempty(m)
84 | m=1;
85 | else
86 | m=m+1;
87 | end
88 |
89 | writeElement(fid, currentElementValue(m),tabs);
90 |
91 | fprintf(fid,'\n%s]\n%s',tabs,tabs);
92 | elseif isstruct(currentElementValue)
93 | fprintf(fid,'"%s" : ',currentField);
94 | writeElement(fid, currentElementValue,tabs);
95 | elseif isnumeric(currentElementValue)
96 | fprintf(fid,'"%s" : %g' , currentField,currentElementValue);
97 | elseif isempty(currentElementValue)
98 | fprintf(fid,'"%s" : null' , currentField,currentElementValue);
99 | else %ischar or something else ...
100 | fprintf(fid,'"%s" : "%s"' , currentField,currentElementValue);
101 | end
102 | end
103 |
104 | % function writeSingleGene(fid,numFields, namesOfFields,dataname,data,m)
105 | %
106 | % for i = 1:numFields - 1
107 | % field = namesOfFields{i};
108 | % stmp = sprintf('%s(m).%s',dataname,field);
109 | % fprintf(fid,'"%s" : "%s",\n' , field,eval(stmp));
110 | % end
111 | % i=i+1;
112 | % field = namesOfFields{i};
113 | % stmp = sprintf('%s(m).%s',dataname,field);
114 | % fprintf(fid,'"%s" : "%s"\n',field,eval(stmp));
115 | %
116 | %
117 | % end
118 |
--------------------------------------------------------------------------------
/src/matlab_octave/install_milp_scheduler.m:
--------------------------------------------------------------------------------
1 | function succ = install_milp_scheduler(modify, verbose)
2 | % Installs milp_scheduler within MATLAB/OCTAVE environment
3 | % Args:
4 | % modify: select how to set path
5 | % 0 (default) - generate relevant ADDPATH() commands, but
6 | % don't execute them
7 | % 1 - modify the path directly executing the relevant
8 | % ADDPATH() commands
9 | % verbose: prints the relevant ADDPATH commands if true (default),
10 | % silent otherwise
11 | % Returns:
12 | % status - SUCCESS : 1 if all commands succeeded, 0 otherwise
13 | %
14 | % Examples:
15 | % install_milp_scheduler; %% print the required ADDPATH() commands
16 | % install_milp_scheduler(0,0); %% save the commands to startup.m, no text output
17 | % install_milp_scheduler(1,0); %% modify PATHS, no text output
18 | % install_milp_scheduler(0,1); %% save the commands to startup.m, print paths to console
19 | % install_milp_scheduler(1,1); %% modify PATHS, print paths to console
20 |
21 | %% installation data for each component
22 | min_ver.Octave = '4.0.0';
23 | min_ver.MATLAB = '7.5.0';
24 | install = struct( ...
25 | 'name', { ...
26 | 'milp-scheduler', ...
27 | 'milp-formulation', ...
28 | 'variable-structures', ...
29 | 'case-studies', ...
30 | 'post-processing',...
31 | 'external'}, ...
32 | 'dirs', { ...
33 | {{''}, {'lib'}}, ...
34 | {{'milp_formulation', ''}, {'milp_formulation', 'lib'}, ...
35 | {'milp_formulation', 'equality_constraints'}, ...
36 | {'milp_formulation', 'inequality_constraints'}}, ...
37 | {{'variable_structures', ''}, {'variable_structures', 'lib'}},...
38 | {{'case_studies', 'two_pump_one_tank'}},...
39 | {{'post_processing', ''}},...
40 | {{'external', 'BuildMPS'}, {'external', 'saveJSONfile'}}}, ...
41 | 'fcns', { ...
42 | {'scheduler'}, ...
43 | {'set_objective_vector', 'set_intcon_vector'}, ...
44 | {'initialise_bin_var_structure', 'initialise_cont_var_structure'}, ...
45 | {'simulator_2p1t'},...
46 | {'plot_optim_outputs'},...
47 | {'saveJSONfile'}} ...
48 | );
49 | ni = length(install); %% number of components
50 |
51 | %% default arguments
52 | interactive = 0;
53 | if nargin < 2
54 | verbose = 1;
55 | if nargin < 1
56 | modify = 1;
57 | end
58 | end
59 |
60 | %% get path to new installation root. Obtain root folder, file_name and extension
61 | [root, n, e] = fileparts(which('install_milp_scheduler'));
62 | %% Add path to access get_system function required for installation.
63 | addpath(fullfile(root, 'lib'), '-end');
64 | %% Check whether the user uses MATLAB or Octave
65 | sw = get_system();
66 |
67 | %% Check for the required version of MATLAB or Octave
68 | vstr = '';
69 | switch sw
70 | case 'Octave'
71 | v = ver('octave');
72 | case 'MATLAB'
73 | v = ver('matlab');
74 | if length(v) > 1
75 | warning('The built-in VER command is behaving strangely, probably as a result of installing a 3rd party toolbox in a directory named ''matlab'' on your path. Check each element of the output of ver(''matlab'') to find the offending toolbox, then move the toolbox to a more appropriately named directory.');
76 | v = v(1);
77 | end
78 | end
79 | if ~isempty(v) && isfield(v, 'Version') && ~isempty(v.Version)
80 | vstr = v.Version;
81 | if vstr2num(vstr) < vstr2num(min_ver.(sw))
82 | error('\n\n%s\n MILP-SCHEDULER requires %s %s or later.\n You are using %s %s.\n%s\n\n', repmat('!',1,45), sw, min_ver.(sw), sw, vstr, repmat('!',1,45));
83 | end
84 | else
85 | warning('\n\n%s\n Unable to determine your %s version. This indicates\n a likely problem with your %s installation.\n%s\n', repmat('!',1,60), sw, sw, repmat('!',1,60));
86 | end
87 |
88 | %% find available new components
89 | available = zeros(ni, 1);
90 | for i = 1:ni
91 | if exist(fullfile(root, install(i).dirs{1}{:}), 'dir')
92 | available(i) = 1;
93 | end
94 | end
95 |
96 | %% Generate paths to add
97 | newpaths = {};
98 | for i = 1:ni
99 | if available(i) %% only available components
100 | for k = 1:length(install(i).dirs)
101 | p = fullfile(root, install(i).dirs{k}{:});
102 | if ~isempty(p) && ~ismember(p, newpaths)
103 | newpaths{end+1} = p;
104 | end
105 | end
106 | end
107 | end
108 |
109 | %% Build addpath string
110 | cmd = sprintf('addpath( ...\n');
111 | for k = 1:length(newpaths)
112 | cmd = sprintf('%s ''%s'', ...\n', cmd, newpaths{k});
113 | end
114 | cmd = sprintf('%s ''%s'' );\n', cmd, '-end');
115 |
116 | div_line = sprintf('\n-------------------------------------------------------------------\n\n');
117 | %% print addpath
118 | if verbose
119 | fprintf(div_line);
120 | if modify
121 | fprintf('Your %s path will be updated using the following command:\n\n%s', sw, cmd);
122 | if interactive
123 | s = input(sprintf('\nHit any key to continue ...'), 's');
124 | end
125 | else
126 | fprintf('Use the following command to add MILP-SCHEDULER to your %s path:\n\n%s\n', sw, cmd);
127 | end
128 | end
129 |
130 | %% add the new paths
131 | if modify
132 | addpath(newpaths{:}, '-end');
133 | if verbose
134 | fprintf('Your %s path has been updated.\n', sw);
135 | end
136 | end
137 |
138 | %% Modify the path if modify flag is set to True
139 | if modify %% modify the path directly
140 | savepath; %% save the updated path
141 | if verbose
142 | fprintf(div_line);
143 | fprintf('Your updated %s path has been saved using SAVEPATH.\n', sw);
144 | end
145 | end
146 |
147 | if verbose
148 | fprintf(div_line);
149 | if modify
150 | fprintf('Now that you have added the required directories to your %s path\n', sw);
151 | fprintf('MILP-SCHEDULER is installed and ready to use.\n\n');
152 | else
153 | fprintf('Once you have added the required directories to your %s path\n', sw);
154 | fprintf('MILP-SCHEDULER will be installed and ready to use.\n\n');
155 | end
156 | end
157 |
158 | if nargout
159 | succ = success;
160 | end
161 |
162 | end
163 |
164 | function num = vstr2num(vstr)
165 | % Converts version string to numerical value suitable for < or > comparisons
166 | % E.g. '3.11.4' --> 3.011004
167 | pat = '\.?(\d+)';
168 | [s,e,tE,m,t] = regexp(vstr, pat);
169 | b = 1;
170 | num = 0;
171 | for k = 1:length(t)
172 | num = num + b * str2num(t{k}{1});
173 | b = b / 1000;
174 | end
175 | end
176 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/abs_error.m:
--------------------------------------------------------------------------------
1 | function y = abs_error(true_value, approx_value)
2 | % Return absolute error between two values
3 | y = abs(true_value - approx_value);
4 | end
5 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/calculate_schedule.m:
--------------------------------------------------------------------------------
1 | function [n, s, x1]= calculate_schedule(model, x0, vars, options)
2 | % Wrapper for MATLAB's intlinprog
3 | % Uses MILP formulation to solve a pump scheduling problem in WDNs
4 |
5 | % Args:
6 | % model - structure with mixed integer linear programme model matrices and vectors
7 | % NOTE: Take care that the model coefficients, such as objective vector,
8 | % matrices A and Aeq, and vectors b and beq are sparse for large problems
9 | % x0: initial point
10 | % vars: Variable structure used for mapping variable indices to vector
11 | % and matrix indices in the milp formulation
12 | % options - intlinprog optimizer options - check https://uk.mathworks.com/help/optim/ug/intlinprog.html#d124e114105
13 |
14 | % Return:
15 | % n, s - vectors of pump states and pump speeds, aka. pump schedule
16 | % x1 - state vector
17 |
18 | fprintf("Solving optimal pump scheduling problem with MATLAB's intlinprog\n");
19 | [x1,fval,exitflag,output]= intlinprog(...
20 | model.c_vector, model.intcon, model.A, model.b, model.Aeq, model.beq, ...
21 | model.lb, model.ub, x0, options);
22 | fprintf("Solver finished executing\n");
23 | fprintf("Exit flag: %d\n", exitflag);
24 | fprintf("Objective value: %f\n", fval);
25 | disp(output);
26 | % Extract vectors of pump stasuses and speeds from the optimal state vector x1
27 | % TODO: Check if `find_schedule_from_x` is generic. It may not be. It may be specific
28 | % to 2p1t case study. In this case, generalize such that `calculate_schedule` is
29 | % a generic function and not specific to any particular case study problem.
30 | [n, s] = find_schedule_from_x(exitflag, vars, x1);
31 | end
32 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/combine_incidence_matrices.m:
--------------------------------------------------------------------------------
1 | function out = combine_incidence_matrices(Lc, Lf)
2 | % Combines the incidence matrices for calculated and
3 | % fixed nodes, respectively
4 | % Returns:
5 | % structure with fields `matrix` with the combined incidence
6 | % matrix and `limits` with two 2x1 arrays of row indices
7 | % defining the ranges of the Lc and Lf matrices within L
8 | out.matrix = [Lc;Lf];
9 | out.limits.Lc = [1 size(Lc,1)];
10 | out.limits.Lf = [size(Lc, 1)+1, size(out.matrix, 1)];
11 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/get_array_indices.m:
--------------------------------------------------------------------------------
1 | function array_indices = get_array_indices(input_array)
2 | % Output a Nx1 cell with indices pointing to all elements of
3 | % a multidimensional array
4 | array_size = size(input_array);
5 | index_vectors = cell(length(array_size), 1);
6 | for n_dim = 1:length(array_size)
7 | index_vectors{n_dim} = 1:array_size(n_dim);
8 | end
9 | if length(index_vectors) == 1
10 | m = ngrid(index_vectors{1});
11 | array_indices = [m(:)];
12 | elseif length(index_vectors) == 2
13 | [m,n] = ndgrid(index_vectors{1},index_vectors{2});
14 | array_indices = [m(:),n(:)];
15 | elseif length(index_vectors) == 3
16 | [m,n,o] = ndgrid(index_vectors{1},index_vectors{2},index_vectors{3});
17 | array_indices = [m(:),n(:),o(:)];
18 | else
19 | error("Only input arrays of dim 1-3 are supported");
20 | array_indices = [];
21 | end
22 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/get_line.m:
--------------------------------------------------------------------------------
1 | function [m, c] = get_line(p1, p2)
2 | % Find coefficients m and c of a line equation in 2D
3 | % y = mx + c from two points in 2D lying on the line
4 | % Args:
5 | % p1 = [x1, y1]
6 | % p2 = [x2, y2]
7 | % Note: points must have a single row and two columns
8 |
9 | % If line vertical, i.e. infinite gradient and zero intercept
10 | if p1(1) == p2(1)
11 | m = nan;
12 | c = p1(1); % Return c as the x coordinate of the vertical line
13 | else
14 | m = (p2(2) - p1(2))/(p2(1)-p1(1));
15 | c = (p1(2)*p2(1) - p2(2)*p1(1))/(p2(1)-p1(1));
16 | end
17 | end
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/get_line_implicit.m:
--------------------------------------------------------------------------------
1 | function [m_x, m_y, c] = get_line_implicit(p1, p2)
2 | % Find coefficients m_x, m_y and c of a line equation in 2D
3 | % m_x * x + m_y * y = c from two points in 2D lying on the line
4 | % Args:
5 | % p1 = [x1, y1]
6 | % p2 = [x2, y2]
7 | % Note: points must have a single row and two columns
8 |
9 | % If line vertical, i.e. infinite gradient and zero intercept
10 | if p1(1) == p2(1)
11 | m_x = 1;
12 | m_y = 0;
13 | c = p1(1); % Return c as the x coordinate of the vertical line
14 | else
15 | m_x = -(p2(2) - p1(2))/(p2(1)-p1(1));
16 | m_y = 1;
17 | c = (p1(2)*p2(1) - p2(2)*p1(1))/(p2(1)-p1(1));
18 | end
19 | end
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/get_plane.m:
--------------------------------------------------------------------------------
1 | function [a, b, c] = get_plane(p1, p2, p3)
2 | % Find coefficients a, b, c of a 2D plane equation in 3D
3 | % z = ax + by + c from three points in 3D lying on that plane
4 | % Args:
5 | % p1 = [x1, y1, z1]
6 | % p2 = [x2, y2, z2]
7 | % p3 = [x3, y3, z3]
8 | % Note: points must have a single row and three columns
9 | % TODO: Modify this code to be generic and accept row and column vectors
10 | p1_p2 = vector_from_points(p1, p2);
11 | p1_p3 = vector_from_points(p1, p3);
12 | [a, b, c] = get_plane_from_vectors(p1_p2', p1_p3, p1);
13 |
14 |
15 | function [a, b, c] = get_plane_from_vectors(v1, v2, p1)
16 | % Find equation of a plane from two vectors lying on that plane and
17 | % one point
18 | % TODO: Make the function generic by checking and adjusting vector and point
19 | % dimensions to assure dimension compatibility
20 |
21 | % Find normal vector n: n(1)∗x + n(2)∗y + n(3)∗z + K = 0
22 | n = cross(v1, v2');
23 | % Trick to make Octave and Matlab compatible because Octave returns a row
24 | % vector whilst Octave returns a column vector
25 | if (size(n,1) < size(n,2))
26 | n = n';
27 | end
28 | % Find offset K
29 | K = -n' * p1';
30 | % Get a, b, c coefficients
31 | a = -n(1)/n(3);
32 | b = -n(2)/n(3);
33 | c = -K/(n(3));
34 | end
35 | end
36 |
37 | function v = vector_from_points(p1, p2)
38 | % Return a vector from point p1 to point p2
39 | if (length(p1) ~= length(p2))
40 | error('Points p1 and p2 have different dimensions');
41 | end
42 | v = p1 - p2;
43 | end
44 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/get_system.m:
--------------------------------------------------------------------------------
1 | function sw = get_system()
2 | % Identify whether we are running MATLAB or OCTAVE
3 | if exist('OCTAVE_VERSION', 'builtin') == 5
4 | sw = 'Octave';
5 | else
6 | sw = 'MATLAB';
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/lin_index_from_array.m:
--------------------------------------------------------------------------------
1 | function index_1d = lin_index_from_array(var_array, index_array)
2 | % Calculate index in a reshaped 1D array from a multi-dimensional array
3 | % base on the sequence of indices pointing to the element in the
4 | % multidimensional array.
5 | % e.g.
6 | % var_array = [2,3,5,8;3,0,-4,6]
7 | % index_array = {2,2} , pointing to the element 0
8 | % 1d (transformed array) = [2,3,3,0,5,-4,8,6]
9 | % index_1d = 2;
10 |
11 | % Carry out data consistency checks
12 | if number_of_dimensions(var_array) ~= length(index_array)
13 | error('Number of indices %d different from of var array dimension - %d', ...
14 | length(index_array), number_of_dimensions(var_array));
15 | end
16 |
17 | if length(index_array) == 1
18 | index_array = {index_array};
19 | end
20 |
21 | if ~iscell(index_array)
22 | error('index_array argument must be a scalar or a cell of values');
23 | end
24 | % Generate a linear (1D) index
25 | index_1d = sub2ind(size(var_array), index_array{:});
26 | end
27 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/lin_index_from_size.m:
--------------------------------------------------------------------------------
1 | function index_1d = lin_index_from_size(array_size, index_array)
2 | % Takes a stacked 1D array that was created from a multi-dimensional array
3 | % with dimensions given in a cell argument orig_dims and finds index
4 | % in the 1D array that corresponds to the element in the multi-dimensional
5 | % array accesed with coordinates provided in index_array
6 | % Args:
7 | % orig_dims: array of original dimensions, e.g. [3,4,2]
8 | % index_array: cell with indices pointing to the element of the original
9 | % multi-dimensional array
10 |
11 | % Argument consistency check
12 | if length(array_size) ~= length(index_array)
13 | error('Array dimensions and index arguments do not have the same lengths.');
14 | end
15 | % Check all indices
16 | for i = 1:length(index_array)
17 | if index_array{i} > array_size(i)
18 | error('Index %d supplied in index array beyond bounds.', index_array{i});
19 | end
20 | end
21 | index_1d = sub2ind(array_size, index_array{:});
22 | end
--------------------------------------------------------------------------------
/src/matlab_octave/lib/number_of_dimensions.m:
--------------------------------------------------------------------------------
1 | function n_dim = number_of_dimensions(input_array, dim_squeeze)
2 | % Finds out the dimensionality of a (multidimensional) array.
3 | %
4 | % Args:
5 | % input_array: a multi-dimensional array or cell
6 | % dim_squeeze [optional]: a boolean flag. If true, the dimensions
7 | % equal 1 are ignored (dropped), e.g. array of size 2, 1, 3 will
8 | % be reported as having 2, instead of 3 dimensions.
9 | % default value == false
10 | %
11 | % Return:
12 | % number of dimensions (integer)
13 |
14 | switch nargin
15 | case 1
16 | dim_squeeze = false;
17 | end
18 |
19 | size_array = size(input_array);
20 | if dim_squeeze == true
21 | n_dim = sum(size_array > 1);
22 | else
23 | n_dim = length(size_array);
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/perc_error.m:
--------------------------------------------------------------------------------
1 | function y = perc_error(true_value, approx_value)
2 | % Return relative error in % between two values
3 | if (true_value == 0) && (approx_value == 0)
4 | y = 0;
5 | return
6 | elseif true_value == 0
7 | den = approx_value;
8 | else
9 | den = true_value;
10 | end
11 | y = 100 * abs((true_value - approx_value)/den);
12 | end
13 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/pipe_characteristic.m:
--------------------------------------------------------------------------------
1 | function dh = pipe_characteristic(R, q)
2 | % Calculate pipe head drop from pipe resistance and flow
3 | dh = R*q*abs(q);
4 | end
5 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/pump_head.m:
--------------------------------------------------------------------------------
1 | function h = pump_head(pump, q, n, s)
2 | % Find head of a group of n equal pumps, each operating at speed s
3 | % q denotes the (total) flow going through the group of pumps
4 | h = pump.A*q^2 + pump.B*q*n*s + pump.C*n^2*s^2;
5 | end
6 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/pump_intercept_flow.m:
--------------------------------------------------------------------------------
1 | function q_int = pump_intercept_flow(pump, n, s)
2 | % Calculate pump intercept flow, i.e. flow for which head produced by n
3 | % equal working pumps at speed s is zero
4 | % Args
5 | % pump - pump data structure
6 | % n - number of working pumps
7 | % s - pump speed (each of the n working pumps has the same speed)
8 | % Solves equation h = pump.A*q^2 + pump.B*q*n*s + pump.C*n^2*s^2
9 | % defined in function pump_head.m for flow q
10 |
11 | delta = (pump.B*n*s)^2 - 4*pump.A*pump.C*n^2*s^2;
12 | q_int = (-pump.B*n*s - sqrt(delta))/(2*pump.A);
13 | end
14 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/pump_nominal_point.m:
--------------------------------------------------------------------------------
1 | function [q, s, H] = pump_nominal_point(pump)
2 | % Get the nominal point on the pump's hydraulic characteristics from pump data
3 | % Assumes nominal speed = 1 and nominal flow equal to maximum efficiency
4 | % flow given in the pump data
5 | q = pump.max_eff_flow;
6 | s = 1.0;
7 | H = pump_head(pump, q, 1, s);
8 | end
9 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/pump_power_consumption.m:
--------------------------------------------------------------------------------
1 | function pump_power = pump_power_consumption(pump, q, s, n)
2 | % Third degree polynomial equation describing power consumption of a group
3 | % of n identical pumps operating at and speed s and flow (through the
4 | % group of pumps) q
5 |
6 | % Uses the following equations
7 | % P(q,n,s) = n * s^3 * P(q/(n*s))
8 | % P(q/(n*s)) = a3 * (q/(n*s))^3 + a2 * (q/(n*s))^2 + a1 * (q/(n*s)) + a0
9 |
10 | % The above equations, when combined, produce the following equation:
11 | % P(q,n,s) = a3*n^(-2)*q^3 + a2*n^(-1)*s*q^2 + a1*n^0*s^2*q + a0*n*s^3
12 |
13 | % where coefficients a3, a2, a1, a0 are denoted as:
14 | % pump.ep, pump.fp, pump.gp and pump.hp respectively.
15 |
16 | % Args:
17 | % pump - structure with pump data
18 | % q - flow going via a group of n working pumps, L/s
19 | % s - pump speed (assumed equal for each working pump in the group), -
20 | % n - number of working pumps in parallel
21 | % Returns
22 | % pump group power consumption in kW
23 |
24 | if n == 0
25 | pump_power = 0;
26 | else
27 | pump_power = pump.ep * n^(-2) * q^3 + pump.fp * 1/n * q^2 * s + ...
28 | pump.gp * q * s^2 + pump.hp * n * s^3;
29 | end
30 |
31 | end
32 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/rem_z_coordinate.m:
--------------------------------------------------------------------------------
1 | function projected_point = rem_z_coordinate(point)
2 | % Remove z coordinate from the point in 3D
3 | projected_point = [point(1), point(2)];
4 | end
5 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/remove_columns.m:
--------------------------------------------------------------------------------
1 | function output_array = remove_columns(input_array, column_indices)
2 | %
3 | output_array = input_array;
4 | output_array(:,column_indices)=[];
5 | end
6 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/remove_rows.m:
--------------------------------------------------------------------------------
1 | function output_array = remove_rows(input_array, row_indices)
2 | %
3 | output_array = input_array;
4 | output_array(row_indices,:)=[];
5 | end
6 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/sub_from_array.m:
--------------------------------------------------------------------------------
1 | function [varargout] = sub_from_array(array, lin_index)
2 | % Finds the array of indices pointing to the element of
3 | % the array that is `pointed to` by a linear index given
4 | % in argument `lin_index'.
5 | % Args:
6 | % array: (multi-dimenionality) array of values
7 | % lin_index: integer determining the linear index, i.e.
8 | % the index to the element of the array reshaped into
9 | % a vector
10 | % Return:
11 | % multi-dimensional index - variable number of arguments
12 | nout = length(size(array));
13 | [varargout{1:nout}] = ind2sub(size(array), lin_index);
14 | end
15 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/sub_from_size.m:
--------------------------------------------------------------------------------
1 | function [varargout] = sub_from_size(array_size, lin_index)
2 | % Takes information about the size of an array and produces
3 | % the array of indices pointing to the element of this
4 | % array that is `pointed to` by a linear index given in
5 | % argument `lin_index'.
6 | % Args:
7 | % array_size: array of integers describing the
8 | % dimensionality of the array
9 | % lin_index: integer determining the linear index, i.e.
10 | % the index to the element of the array reshaped into
11 | % a vector
12 | % Return:
13 | % multi-dimensional index - variable number of arguments
14 | nout = length(array_size);
15 | [varargout{1:nout}] = ind2sub(array_size, lin_index);
16 | end
17 |
--------------------------------------------------------------------------------
/src/matlab_octave/lib/vol_to_h_constant_area.m:
--------------------------------------------------------------------------------
1 | function h = vol_to_h_constant_area(A, V, elev)
2 | % Calculate tank head from fill volume for a tank with constant surface area
3 | % Args:
4 | % A - tank area, m2
5 | % V - tank fill volume, m3
6 | % elev - tank elevation in metres (optional)
7 | % Return
8 | % tank head in metres
9 |
10 | if nargin == 2
11 | elev = 0;
12 | end
13 |
14 | h = V / A + elev;
15 | end
16 |
17 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/README.md:
--------------------------------------------------------------------------------
1 | # Module for formulating the mixed integer linear programme structures
2 | ## Linearization of model components
3 | ### Pipes
4 |
5 |
6 |
7 |
8 | ### Pump characteristics
9 | 
10 | ### Pump power consumption
11 |
12 |
13 | ## Cost function
14 | ```math
15 | \begin{equation}
16 | J = \sum_{k=1}^{N_T} \sum_{j=1}^{n_{pumps}} T(k) \; p^j(k) \, \Delta t
17 | \end{equation}
18 | ```
19 | ## Equality constraints
20 | see documentation [here](equality_constraints/README.md)
21 |
22 | ## Inequality constraints
23 | see documentation [here](inequality_constraints/README.md)
24 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/equality_constraints/README.md:
--------------------------------------------------------------------------------
1 | ## Equality constraints (for each time-step $k$)
2 |
3 | ### 1. Mass flow balances in network nodes - *nodeq_contraints.m*
4 | ```math
5 | \begin{equation}
6 | \sum_{m \in \mathbf{Q_{in,j}}} q_p^m(k) - \sum_{n \in \mathbf{Q_{out,j}}} q_p^n (k) - d_j(k) = 0 \quad \forall j \in \mathbf{I}_{nodes} \quad \forall k \in \{1 \ldots N_T\}
7 | \end{equation}
8 | ```
9 | ### 2. Headlosses in network pipes - *pipe_headloss_constraints.m*
10 | ```math
11 | \begin{equation}
12 | h_o^j(k) - h_d^j(k) - \sum_{i=1}^{n_{s,pipe}} {m_i^j \, ww_i^j(k)} - \sum_{i=1}^{n_{s,pipe}} {c_i^j \, bb_i^j(k)} = 0 \quad \forall j \in \mathbf{I}_{pipes} \quad \forall k \in \{1 \ldots N_T\}
13 | \end{equation}
14 | ```
15 | ### 3. Link between flows in pump groups (network elements) and flows in (individual) pumps - *pumpgroup_constraints.m*
16 | ```math
17 | \begin{equation}
18 | \sum_{i=1}^{n_p^j} q_i^j(k) \, n_i^j(k) = q_{el}^j \quad \forall j \in \mathbf{I}_{pumpgroups}
19 | \end{equation}
20 | ```
21 | ### 4. Relationship between water mass balance in tanks and tank levels - *ht_constraints.m*
22 | ```math
23 | \begin{equation}
24 | h_t^j(k) = \left\{
25 | \begin{array}{ll}
26 | h_t^j(k-1) - \frac{1}{A_t^j} \, q_{tank}^j(k) = 0 & \mathrm{for} \quad k = 2:N_T\\
27 | z_t^j + x_0^j & \mathrm{for} \quad k = 1
28 | \end{array}
29 | \right. \quad \forall j \in \mathbf{I}_{tanks}
30 | \end{equation}
31 | ```
32 | ### 5. Pipe segment flow equality constraint - *ww_constraints.m*
33 | ```math
34 | \begin{equation}
35 | q_p^j(k) - \sum_{i=1}^{n_{s,pipe}} ww_i^j(k) = 0 \quad \forall j \in \mathbf{I}_{pipes}
36 | \end{equation}
37 | ```
38 | ### 6. Pipe segment selection equality constraint - *bb_constraints.m*
39 | ```math
40 | \begin{equation}
41 | \sum_{i=1}^{n_{s,pipe}} bb_i^j(k) = 1 \quad \forall j \in \mathbf{I}_{pipes}
42 | \end{equation}
43 | ```
44 | ### 7. Pump segment flow equality constraint - *qq_constraints.m*
45 | ```math
46 | \begin{equation}
47 | q^j(k) - \sum_{i=1}^{n_{s,pump}} qq_i^j(k) = 0 \quad \forall j \in \mathbf{I}_{pumps}
48 | \end{equation}
49 | ```
50 | ### 8. Pump segment speed equality constraint - *ss_constraints.m*
51 | ```math
52 | \begin{equation}
53 | s^j(k) - \sum_{i=1}^{n_{s,pump}} ss_i^j(k) = 0 \quad \forall j \in \mathbf{I}_{pumps}
54 | \end{equation}
55 | ```
56 | ### 9. Pump segment selection equality constraint - *aa_constraints.m*
57 | ```math
58 | \begin{equation}
59 | n^j(k) - \sum_{i=1}^{n_{s,pump}} aa_i^j(k) = 0 \quad \forall j \in \mathbf{I}_{pumps}
60 | \end{equation}
61 | ```
62 |
63 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/equality_constraints/aa_constraints.m:
--------------------------------------------------------------------------------
1 | function [Aeq_aa, beq_aa] = aa_constraints(vars)
2 | % Applies constraints to pump segment selection variables aa
3 | % for all k = 1..K for all j in E_{pump} \sum_i^{n_seg} aa_i^j - n_j = 0
4 |
5 | % Args:
6 | % vars: variables structure with the variables that are ultimately converted
7 | % onto the variable vector x
8 |
9 | number_time_steps = size(vars.x_bin.aa, 2);
10 | number_pumps = size(vars.x_bin.n, 2);
11 | number_segments = size(vars.x_bin.aa, 1);
12 | % Initialize output arrays
13 | Aeq_aa = zeros(number_time_steps*number_pumps, var_struct_length(vars));
14 | beq_aa = zeros(number_time_steps,number_pumps);
15 | row_counter = 1;
16 | % TODO: This loop will need to be generalized into groups and pumps per group
17 | % and the variable n will have to be a 3D array with additional
18 | % dimension defining every pump group in the model
19 | for j = 1:number_pumps
20 | for i = 1:number_time_steps
21 | Aeq = vars;
22 | Aeq.x_bin.aa(:,i,j) = 1;
23 | Aeq.x_bin.n(i,j) = -1;
24 | Aeq_aa(row_counter,:) = struct_to_vector(Aeq)';
25 | beq_aa(i,j)=0;
26 | row_counter = row_counter + 1;
27 | end
28 | end
29 | beq_aa = beq_aa(:);
30 | end
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/equality_constraints/bb_constraints.m:
--------------------------------------------------------------------------------
1 | function [Aeq_bb, beq_bb] = bb_constraints(vars)
2 | % Sum of bb variables bb_i^j(k) over all segments i for each pipe j for every
3 | % time step k = 1...K == 1
4 |
5 | % Args:
6 | % vars: variables structure with the variables that are ultimately converted
7 | % onto the variable vector x
8 |
9 | number_time_steps = size(vars.x_bin.bb, 2);
10 | number_pipes = size(vars.x_bin.bb, 3);
11 | number_segments = size(vars.x_bin.bb, 1);
12 |
13 | % Initialize output arrays
14 | Aeq_bb = zeros(number_time_steps*number_pipes, var_struct_length(vars));
15 | beq_bb = zeros(number_time_steps,number_pipes);
16 | row_counter = 1;
17 | for j = 1:number_pipes
18 | for i = 1:number_time_steps
19 | Aeq = vars;
20 | Aeq.x_bin.bb(:,i,j) = 1;
21 | beq = 1;
22 | % Add the new matrix and vector to the output arrays
23 | Aeq_bb(row_counter,:) = struct_to_vector(Aeq)';
24 | beq_bb(i,j)=beq;
25 | row_counter = row_counter + 1;
26 | end
27 | end
28 | % Roll out the beq vector
29 | beq_bb = beq_bb(:);
30 | end
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/equality_constraints/ht_constraints.m:
--------------------------------------------------------------------------------
1 | function [Aeq_ht, beq_ht] = ht_constraints(vars, network)
2 | % Set the equality constraints determining the water mass balance
3 | % in the tank(s) for all time steps except the first. In the first time-step
4 | % tank heads ht_i(k) for i = 1:Ntanks are set to ht_init, i.e. the initial
5 | % heads.
6 | % Map equation: ht(k) - ht(k-1) - 1/At q_tank(k) == 0 for k = 2:N_TIMESTEPS
7 | % ht(1) = tank_elevation + init_level
8 |
9 | % Args:
10 | % vars: variables structure with the variables that are ultimately converted
11 | % onto the variable vector x
12 | % network: structure with network variables
13 |
14 | % TODO: Expand this code to multiple tanks
15 | tank = network.tanks(1);
16 |
17 | number_time_steps = size(vars.x_cont.ht, 1);
18 | % Initialize output arrays
19 | Aeq_ht = zeros(number_time_steps, var_struct_length(vars));
20 | beq_ht = zeros(number_time_steps, 1);
21 |
22 | for i = 1:number_time_steps
23 | Aeq = vars;
24 | Aeq.x_cont.ht(i) = 1;
25 | if i == 1
26 | % Set initial condition for tank elevation
27 | beq = tank.elevation + tank.init_level;
28 | else
29 | Aeq.x_cont.ht(i-1)=-1;
30 | Aeq.x_cont.qel(i, network.elements.tank_feed_pipe_index) = -1/tank.area;
31 | beq = 0;
32 | end
33 | Aeq_ht(i,:) = struct_to_vector(Aeq)';
34 | beq_ht(i) = beq;
35 | end
36 | end
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/equality_constraints/nodeq_constraints.m:
--------------------------------------------------------------------------------
1 | function [Aeq_nodeq, beq_nodeq] = nodeq_constraints(vars, network, input)
2 | % Apply flow mass balance constraints in all nodes
3 | % For every node j for all time steps k = 1 .. N
4 | % \sum q_{in}^j (k) - \sum q_{out}^j (k) = 0
5 | Lc = network.Lc;
6 | no_equalities = size(Lc,1);
7 | number_time_steps = size(vars.x_cont.qel,1);
8 | % Initialize output arrays
9 | Aeq_nodeq = zeros(no_equalities*number_time_steps, var_struct_length(vars));
10 | beq_nodeq = zeros(no_equalities, number_time_steps)';
11 | row_counter = 1;
12 | for j = 1:no_equalities
13 | for i = 1:number_time_steps
14 | Aeq=vars;
15 | in_flows = find(Lc(j,:)==1);
16 | out_flows = find(Lc(j,:)==-1);
17 | Aeq.x_cont.qel(i,in_flows) = 1;
18 | Aeq.x_cont.qel(i,out_flows) = -1;
19 | d_ji = input.demands(j,i) * input.df;
20 | Aeq_nodeq(row_counter,:) = struct_to_vector(Aeq)';
21 | beq_nodeq(i,j)=d_ji;
22 | row_counter = row_counter + 1;
23 | end
24 | end
25 | beq_nodeq = tensor_to_vector(beq_nodeq); %beq_nodeq(:);
26 | end
27 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/equality_constraints/pipe_headloss_constraints.m:
--------------------------------------------------------------------------------
1 | function [Aeq_dhpipe, beq_dhpipe] = pipe_headloss_constraints(vars, network, ...
2 | input, lin_pipes)
3 | % Applies the pipe headloss equality condition for each pipe j and each time
4 | % step k
5 | % h_o^j (k) − h_d^j (k) − \sum_{i=1}^{n_{seg}} {m_i^j \, ww_i^j} - \sum_{i=1}^n_{seg}} {c_i^j \, bb_i^j} =
6 |
7 | % Args:
8 | % vars: variables structure with the variables that are ultimately converted
9 | % onto the variable vector x
10 | % network: structure with network variables
11 | % input: input structure
12 | % lin_pipes: array of structs obtained from `linearize_pipes` that contain
13 | % information about line coefficients and limits of each segment of each
14 | % linearized pipe in the network
15 |
16 | number_time_steps = size(vars.x_cont.ww, 2);
17 | number_pipes = size(vars.x_cont.ww, 3);
18 | number_segments = size(vars.x_cont.ww, 1);
19 | % Prepare incidence matrices without non-pipe elements
20 | % TODO: MAKE THIS CODE WORK WITH MORE THAN ONE PUMP GROUP
21 | % Remove column(s) from incidence matrices that correspond to pump group elements
22 | Lc = remove_columns(network.Lc, network.pump_group_indices(1));
23 | Lf = remove_columns(network.Lf, network.pump_group_indices(1));
24 | L = combine_incidence_matrices(Lc, Lf);
25 | % Initialize output arrays
26 | Aeq_dhpipe = zeros(number_time_steps*number_pipes, var_struct_length(vars));
27 | beq_dhpipe = zeros(number_time_steps,number_pipes);
28 | % Get the upstream and downstream head for each pipe
29 | row_counter = 1;
30 | for j = 1:number_pipes
31 | % Find the upstream and downstream head indices
32 | index_up_head = find(L.matrix(:,j)==-1);
33 | index_down_head = find(L.matrix(:,j)==1);
34 | for i = 1:number_time_steps
35 | Aeq = vars;
36 | beq = 0;
37 | % This bit is not generic, and repetitive
38 | % TODO: Provide a structure that contains information about which indices
39 | % Correspond to pipes/other elements and calculated nodes/tank nodes/reservoir nodes
40 | if (index_up_head <= L.limits.Lc(end)) && (index_up_head >= 1)
41 | Aeq.x_cont.hc(i,index_up_head) = 1;
42 | beq = 0;
43 | elseif (index_up_head == L.limits.Lf(1))
44 | % Reservoir
45 | beq = - input.hr;
46 | elseif (index_up_head == L.limits.Lf(2))
47 | % Tank
48 | Aeq.x_cont.ht(i) = 1;
49 | beq = 0;
50 | else
51 | error("Encountered unknown index %d in the incidence matrix", index_up_head);
52 | end
53 | % Same as above, but for downstream node indices
54 | if (index_down_head <= L.limits.Lc(end)) && (index_down_head >= 1)
55 | Aeq.x_cont.hc(i,index_down_head) = -1;
56 | beq = beq + 0;
57 | elseif (index_down_head == L.limits.Lf(1))
58 | beq = beq + input.hr;
59 | elseif (index_down_head == L.limits.Lf(2))
60 | Aeq.x_cont.ht(i) = -1;
61 | beq = beq + 0;
62 | else
63 | error("Encountered unknown index %d in the incidence matrix", index_down_head);
64 | end
65 |
66 | for k = 1:number_segments
67 | m = lin_pipes{j}(k).coeffs.m;
68 | c = lin_pipes{j}(k).coeffs.c;
69 | Aeq.x_cont.ww(k,i,j) = -m;
70 | Aeq.x_bin.bb(k,i,j) = -c;
71 | end
72 | % Add the new matrix and vector to the output arrays
73 | Aeq_dhpipe(row_counter,:) = struct_to_vector(Aeq)';
74 | beq_dhpipe(i,j)=beq;
75 | % Assign beq
76 | row_counter = row_counter + 1;
77 | end
78 | end
79 | % Roll out the beq vector
80 | beq_dhpipe = tensor_to_vector(beq_dhpipe);
81 | end
82 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/equality_constraints/pumpgroup_constraints.m:
--------------------------------------------------------------------------------
1 | function [Aeq_pumpgroup, beq_pumpgroup] = pumpgroup_constraints(vars, network)
2 | % Apply constraints linking flows in pump groups to flows in (individual) pumps
3 | % For each pump group j in E_{pump_group} \sum_i^j (q_i^j) * n_i^j = q_{pumpgroup}
4 | % for j in E_{pumps, j}
5 | no_pump_groups = length(network.pump_groups);
6 | number_time_steps = size(vars.x_cont.qel, 1);
7 | Aeq_pumpgroup = zeros(no_pump_groups * number_time_steps, ...
8 | var_struct_length(vars));
9 | beq_pumpgroup = zeros(no_pump_groups, number_time_steps)';
10 | row_counter = 1;
11 | pump_flow_index = 1;
12 | for j = 1:no_pump_groups
13 | for i = 1:number_time_steps
14 | Aeq=vars;
15 | pump_group_index = network.pump_groups(j).element_index;
16 | % Make this code generalize for more pump groups
17 | npumps = network.pump_groups(j).npumps;
18 | Aeq.x_cont.qel(i,pump_group_index) = 1;
19 | Aeq.x_cont.q(i,pump_flow_index:npumps) = -1;
20 | beq_pumpgroup(i,j)=0;
21 | Aeq_pumpgroup(row_counter,:) = struct_to_vector(Aeq)';
22 | row_counter = row_counter + 1;
23 | end
24 | pump_flow_index = pump_flow_index + npumps;
25 | end
26 | beq_pumpgroup = tensor_to_vector(beq_pumpgroup);
27 | end
28 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/equality_constraints/qq_constraints.m:
--------------------------------------------------------------------------------
1 | function [Aeq_qq, beq_qq] = qq_constraints(vars)
2 | % Applies constraints to pump segment flows
3 | % for all pumps for all time steps = 1:K, sum_i qq_i^j(k) - q_j(k) = 0
4 | %
5 | % Args:
6 | % vars: variables structure with the variables that are ultimately converted
7 | % onto the variable vector x
8 |
9 | number_time_steps = size(vars.x_cont.qq, 2);
10 | number_pumps = size(vars.x_cont.qq, 3);
11 | number_segments = size(vars.x_cont.qq, 1);
12 | % Initialize output arrays
13 | Aeq_qq = zeros(number_time_steps*number_pumps, var_struct_length(vars));
14 | beq_qq = zeros(number_time_steps,number_pumps);
15 | row_counter = 1;
16 | for j = 1:number_pumps
17 | for i = 1:number_time_steps
18 | Aeq = vars;
19 | Aeq.x_cont.qq(:,i,j) = -1;
20 | Aeq.x_cont.q(i,j) = 1;
21 | Aeq_qq(row_counter,:) = struct_to_vector(Aeq)';
22 | beq_qq(i,j)=0;
23 | row_counter = row_counter + 1;
24 | end
25 | end
26 | beq_qq = beq_qq(:);
27 | end
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/equality_constraints/ss_constraints.m:
--------------------------------------------------------------------------------
1 | function [Aeq_ss, beq_ss] = ss_constraints(vars)
2 | % Applies constraints to pump segment speeds
3 | % for all pumps for all time steps = 1:K, sum_i ss_i^j(k) - s_j(k) = 0
4 | % Args:
5 | % vars: variables structure with the variables that are ultimately converted
6 | % onto the variable vector x
7 |
8 | number_time_steps = size(vars.x_cont.ss, 2);
9 | number_pumps = size(vars.x_cont.ss, 3);
10 | number_segments = size(vars.x_cont.ss, 1);
11 | % Initialize output arrays
12 | Aeq_ss = zeros(number_time_steps*number_pumps, var_struct_length(vars));
13 | beq_ss = zeros(number_time_steps,number_pumps);
14 | row_counter = 1;
15 | for j = 1:number_pumps
16 | for i = 1:number_time_steps
17 | Aeq = vars;
18 | Aeq.x_cont.ss(:,i,j) = -1;
19 | Aeq.x_cont.s(i,j) = 1;
20 | Aeq_ss(row_counter,:) = struct_to_vector(Aeq)';
21 | beq_ss(i,j)=0;
22 | row_counter = row_counter + 1;
23 | end
24 | end
25 | beq_ss = beq_ss(:);
26 | end
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/equality_constraints/ww_constraints.m:
--------------------------------------------------------------------------------
1 | function [Aeq_qpipe, beq_qpipe] = ww_constraints(vars, network)
2 | % For each pipe the flow qel that represents a pipe (note: qel represents
3 | % flows in pipes and pump groups) has to be equal to the sum of segment flows
4 | % qpipe_j(k) - \sum_i ww{i,j}(k) = 0 for all pipe segments i for all pipes j
5 | % and all time steps k = 1 : K
6 | % where qpipe(j) = qel(pipe_indices(j)), i.e. pipe flows are stored within the
7 | % vector of elements flows qel
8 |
9 | % Args:
10 | % vars: variables structure with the variables that are ultimately converted
11 | % onto the variable vector x
12 | % network: structure with network variables
13 |
14 | number_time_steps = size(vars.x_cont.qel, 1);
15 | number_pipes = length(network.pipe_indices);
16 |
17 | % Initialize output arrays
18 | Aeq_qpipe = zeros(number_time_steps*number_pipes, var_struct_length(vars));
19 | beq_qpipe = zeros(number_time_steps,number_pipes);
20 |
21 | row_counter = 1;
22 | for j = 1:number_pipes
23 | pipe_index = network.pipe_indices(j); % Pipe index in the vector of element flows qel
24 | for i = 1:number_time_steps
25 | Aeq = vars;
26 | Aeq.x_cont.qel(i,pipe_index) = 1;
27 | Aeq.x_cont.ww(:,i,j) = -1;
28 | beq = 0;
29 | % Add the new matrix and vector to the output arrays
30 | Aeq_qpipe(row_counter,:) = struct_to_vector(Aeq)';
31 | beq_qpipe(i,j)=beq;
32 | row_counter = row_counter + 1;
33 | end
34 | end
35 | % Roll out the beq vector
36 | beq_qpipe = beq_qpipe(:);
37 |
38 | end
39 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/inequality_constraints/README.md:
--------------------------------------------------------------------------------
1 | ## Inequality constraints (for each time-step $k$)
2 |
3 | ### 1. Linear power consumption model inequality constraint *power_model_ineq_constraint.m*
4 | ```math
5 | \begin{equation}
6 | (n^j(k)-1) \, U_{power} \le \left( m^j_q \, q^j(k) + m^j_s \,s^j(k) + c^j \right) - p^j(k) \le (1-n^j(k)) \, U_{power}
7 | \end{equation}
8 | ```
9 | The above LHS and RHS inequalities can be represented as:
10 | ```math
11 | \begin{equation}
12 | \left\{
13 | \begin{array}{lll}
14 | -m^j_q \, q^j(k) - m^j_s \,s^j(k) + p^j(k)& \le & U_{power} + c^j \\
15 | + m^j_q \, q^j(k) + m^j_s \,s^j(k) - p^j(k) & \le & U_{power} - c^j
16 | \end{array}
17 | \right. \quad \forall j \in \mathbf{I}_{pumps}
18 | \end{equation}
19 | ```
20 | ### 2. Binary linearization of the power consumption variable with respect to pump status - *power_ineq_constraint.m*
21 | ```math
22 | \begin{equation}
23 | 0 \le p^j(k) \le n^j(k) \, U_{power}
24 | \end{equation}
25 | ```
26 | which translate into the following two single-sided inequalities:
27 | ```math
28 | \begin{equation}
29 | \left\{
30 | \begin{array}{lll}
31 | -p^j(k) & \le & 0 \\
32 | +p^j(k) - U_{power} \, n^j(k) & \le & 0
33 | \end{array}
34 | \right. \quad \forall j \in \mathbf{I}_{pumps}
35 | \end{equation}
36 | ```
37 | ### 3. Boundary constraints for pipe segment flows - *pipe_flow_segment_constraints.m*
38 | ```math
39 | \begin{equation}
40 | bb_i^j(k) \, q^j_{i,min} \le ww_i^j(k) \le bb_i^j(k) \, q^j_{i,max}
41 | \end{equation}
42 | ```
43 | which translate into the following two single-sided inequalities:
44 | ```math
45 | \begin{equation}
46 | \left\{
47 | \begin{array}{lll}
48 | -ww_i^j(k) + q^j_{i,min} \, bb_i^j(k) & \le & 0 \\
49 | +ww_i^j(k) - q^j_{i,max} \, bb_i^j(k) & \le & 0
50 | \end{array}
51 | \right. \quad \forall j \in \mathbf{I}_{pipes} \quad \forall i \in \mathbf{I}_{pipesegments}
52 | \end{equation}
53 | ```
54 | ### 4. Linearized pump speed inequality constraint - *s_box_constraints.m*
55 | ```math
56 | \begin{equation}
57 | n^j(k) \, s^j_{min} \le s^j(k) \le n^j(k) \, s^j_{max}
58 | \end{equation}
59 | ```
60 | which translate into the following two single-sided inequalities:
61 | ```math
62 | \begin{equation}
63 | \left\{
64 | \begin{array}{lll}
65 | -s^j(k) + s^j_{min} \, n^j(k) & \le & 0 \\
66 | +s^j(k) - s^j_{max} \, n^j(k) & \le & 0
67 | \end{array}
68 | \right. \quad \forall j \in \mathbf{I}_{pumps}
69 | \end{equation}
70 | ```
71 | ### 5. Linearized pump flow inequality constraint - *q_box_constraints.m*
72 | ```math
73 | \begin{equation}
74 | 0 \le q^j(k) \le n^j(k) \, q^j_{intcpt}(s^j_{max})
75 | \end{equation}
76 | ```
77 | which translate into the following two single-sided inequalities:
78 | ```math
79 | \begin{equation}
80 | \left\{
81 | \begin{array}{lll}
82 | -q^j(k) & \le & 0 \\
83 | +q^j(k) - q^j_{intcpt}(s^j_{max}) \, n^j(k) & \le & 0
84 | \end{array}
85 | \right. \quad \forall j \in \mathbf{I}_{pumps}
86 | \end{equation}
87 | ```
88 | ### 6. Linearized pump hydraulics constraints - *pump_equation_constraints.m*
89 | ```math
90 | \begin{equation}
91 | -(1-n^j(k)) \, U_{pump} \le \Delta h(k) - \Delta h_{pump}(k) \le (1-n^j(k)) \, U_{pump}
92 | \end{equation}
93 | ```
94 | where
95 | ```math
96 | \begin{equation}
97 | \Delta h_{pump}(k) = \sum_{i=1}^{n_{s,pump}} \left( dd^j_i \, ss^j_i(k) + ee^j_i \, qq^j_i(k) + ff^j_i \, aa^j_i(k) \right)
98 | \end{equation}
99 | ```
100 | which translate into the following two single-sided inequalities:
101 | ```math
102 | \begin{equation}
103 | \left\{
104 | \begin{array}{lll}
105 | -\Delta h(k) + \Delta h_{pump}(k) + U_{pump} \, n^j(k) & \le & U_{pump} \\
106 | +\Delta h(k) - \Delta h_{pump}(k) + U_{pump} \, n^j(k) & \le & U_{pump}
107 | \end{array}
108 | \right. \quad \forall j \in \mathbf{I}_{pumps}
109 | \end{equation}
110 | ```
111 | ### 7. Linearized pump characteristic domain constraints - *pump_domain_constraints.m*
112 | ```math
113 | \begin{equation}
114 | m_{ss,i}^n \, ss_i^j(k) + m_{qq,i}^n \, qq_i^j(k) + c_i^n \le 0 \quad \forall i \in \mathbf{I}_{s,pump} \quad \forall j \in \mathbf{I}_{pumps} \quad \forall n \in \{1 \ldots 4\}
115 | \end{equation}
116 | ```
117 |
118 | ### 8. Setting preferences for pump selection within groups of equal pumps to break problem symmetry - *pump_symmetry_breaking.m*
119 | ```math
120 | \begin{equation}
121 | -n^{j+1}j(k) + n^j(k) \le 0 \quad \forall j \in \{1 \ldots (n_{pumps}-1)\} \quad \textrm{for each pump group with equal pumps}
122 | \end{equation}
123 | ```
124 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/inequality_constraints/pipe_flow_segment_constraints.m:
--------------------------------------------------------------------------------
1 | function [A_pipe, b_pipe] = pipe_flow_segment_constraints(vars, lin_pipes, linprog)
2 | % Set the inequality constraints for pipe segment flows
3 | % bb(i,k,j) * q*(i-1,j) <= ww(i,k,j) <= bb(i,k,j) * q*(i,j)
4 | % where q* flows are the boundary points defining the `domains` of validity
5 | % for each segment in the linearized pipe characteristic.
6 | % Number of inequalities are equal to n_pipes * n_segments * N * 2 where the
7 | % last coefficient 2 describes the fact that we have two inequalities per
8 | % equation: the lower bound and the upper bound.
9 |
10 | % LH inequality:
11 | % -wwij(k) + bbij(k) qj(i-1)* <= 0
12 | % RH inequality:
13 | % wwij(k) - bbij(k) qj(i)* <= 0
14 | number_time_steps = size(vars.x_cont.ww, 2);
15 | number_pipes = size(vars.x_cont.ww, 3);
16 |
17 | A_lh = zeros(number_time_steps*number_pipes*linprog.NO_PIPE_SEGMENTS,...
18 | var_struct_length(vars));
19 | A_rh = zeros(number_time_steps*number_pipes*linprog.NO_PIPE_SEGMENTS,...
20 | var_struct_length(vars));
21 |
22 | b_lh = zeros(number_time_steps, number_pipes, linprog.NO_PIPE_SEGMENTS);
23 | b_rh = zeros(number_time_steps, number_pipes, linprog.NO_PIPE_SEGMENTS);
24 |
25 | %LHS inequality
26 | row_counter = 1;
27 | for i = 1:linprog.NO_PIPE_SEGMENTS
28 | for j = 1:number_pipes
29 | for k = 1:number_time_steps
30 | left_limit = lin_pipes{j}(i).limits{1}(1);
31 | Aineq = vars;
32 | Aineq.x_cont.ww(i,k,j) = -1;
33 | Aineq.x_bin.bb(i,k,j) = left_limit;
34 | b_lh(k,j,i) = 0;
35 | A_lh(row_counter,:) = struct_to_vector(Aineq)';
36 | row_counter = row_counter + 1;
37 | end
38 | end
39 | end
40 | b_lh = b_lh(:);
41 |
42 | %RHS inequality
43 | row_counter = 1;
44 | for i = 1:linprog.NO_PIPE_SEGMENTS
45 | for j = 1:number_pipes
46 | for k = 1:number_time_steps
47 | right_limit = lin_pipes{j}(i).limits{2}(1);
48 | Aineq = vars;
49 | Aineq.x_cont.ww(i,k,j) = +1;
50 | Aineq.x_bin.bb(i,k,j) = -right_limit;
51 | b_rh(k,j,i) = 0;
52 | A_rh(row_counter,:) = struct_to_vector(Aineq)';
53 | row_counter = row_counter + 1;
54 | end
55 | end
56 | end
57 | b_rh = b_rh(:);
58 |
59 | A_pipe = [A_lh; A_rh];
60 | b_pipe = [b_lh; b_rh];
61 |
62 | end
63 |
64 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/inequality_constraints/power_ineq_constraint.m:
--------------------------------------------------------------------------------
1 | function [A_p, b_p] = power_ineq_constraint(vars, linprog)
2 | % Set the inequality constraints
3 | % p_j(k) - n_j(k) U_power <= 0 (1)
4 | % -p_j(k) <= 0 (2)
5 | % for every pump j for every time step k
6 | % The inequality is used fo `binary linearisation’ of power consumption output
7 | % with respect to the pump (on/off) status.
8 | number_time_steps = size(vars.x_cont.p, 1);
9 | number_pumps =size(vars.x_cont.p, 2);
10 |
11 | % Initialize output arrays
12 | A_p = zeros(2*number_time_steps*number_pumps, var_struct_length(vars));
13 | b_p = zeros(number_time_steps,number_pumps, 2);
14 |
15 | row_counter = 1;
16 | % Eq. 1
17 | for j = 1:number_pumps
18 | for i = 1:number_time_steps
19 | Aineq = vars;
20 | Aineq.x_cont.p(i,j) = 1;
21 | Aineq.x_bin.n(i,j) = - linprog.Upower;
22 | bineq = 0;
23 | A_p(row_counter,:) = struct_to_vector(Aineq)';
24 | b_p(i,j,1)=bineq;
25 | row_counter = row_counter + 1;
26 | end
27 | end
28 |
29 | % Eq. 2
30 | for j = 1:number_pumps
31 | for i = 1:number_time_steps
32 | Aineq = vars;
33 | Aineq.x_cont.p(i,j) = -1;
34 | bineq = 0;
35 | A_p(row_counter,:) = struct_to_vector(Aineq)';
36 | b_p(i,j,2)=bineq;
37 | row_counter = row_counter + 1;
38 | end
39 | end
40 | % Roll out the b vector
41 | b_p = b_p(:);
42 | end
43 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/inequality_constraints/power_model_ineq_constraint.m:
--------------------------------------------------------------------------------
1 | function [A_p_lin, b_p_lin] = power_model_ineq_constraint(vars, lin_power, linprog)
2 | % Implement the inequality constraints on pump power using the big U trick
3 | % - m_s * sj(k) - m_q * qj(k) + pj(k) + nj(k) Upower <= U_power + c (LH constraint)
4 | % m_s * sj(k) + m_q * qj(k) + -pj(k) + nj(k) Upower <= Upower -c (RH constraint)
5 | % Initialize the left and right hand constraints
6 | number_time_steps = size(vars.x_cont.p, 1);
7 | number_pumps =size(vars.x_cont.p, 2);
8 | A_lh = zeros(number_time_steps*number_pumps,var_struct_length(vars));
9 | A_rh = zeros(number_time_steps*number_pumps,var_struct_length(vars));
10 | b_lh = zeros(number_time_steps,number_pumps);
11 | b_rh = zeros(number_time_steps,number_pumps);
12 |
13 | m_dq = lin_power.coeff(1);
14 | m_ds = lin_power.coeff(2);
15 | c = lin_power.coeff(3);
16 |
17 | % LH inequality
18 | row_counter = 1;
19 | for j = 1:number_pumps
20 | for i = 1:number_time_steps
21 | Aineq = vars;
22 | Aineq.x_cont.s(i,j) = -m_ds;
23 | Aineq.x_cont.q(i,j) = -m_dq;
24 | Aineq.x_cont.p(i,j) = 1;
25 | Aineq.x_bin.n(i,j) = linprog.Upower;
26 | bineq = linprog.Upower + c;
27 | A_lh(row_counter,:) = struct_to_vector(Aineq)';
28 | b_lh(i,j)=bineq;
29 | row_counter = row_counter + 1;
30 | end
31 | end
32 | % Roll out the b vector
33 | b_lh = b_lh(:);
34 |
35 | % RH inequality
36 | row_counter = 1;
37 | for j = 1:number_pumps
38 | for i = 1:number_time_steps
39 | Aineq = vars;
40 | Aineq.x_cont.s(i,j) = m_ds;
41 | Aineq.x_cont.q(i,j) = m_dq;
42 | Aineq.x_cont.p(i,j) = -1;
43 | Aineq.x_bin.n(i,j) = linprog.Upower;
44 | bineq = linprog.Upower - c;
45 | A_rh(row_counter,:) = struct_to_vector(Aineq)';
46 | b_rh(i,j)=bineq;
47 | row_counter = row_counter + 1;
48 | end
49 | end
50 | % Roll out the b vector
51 | b_rh = b_rh(:);
52 |
53 | A_p_lin = [A_lh; A_rh];
54 | b_p_lin = [b_lh; b_rh];
55 | end
56 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/inequality_constraints/pump_domain_constraints.m:
--------------------------------------------------------------------------------
1 | function [A_pump_domain, b_pump_domain] = pump_domain_constraints(vars, ...
2 | linprog, lin_pumps)
3 | % Constraints for each of the domains of the linerized pump characteristic
4 | % equation
5 | % Di,j * SQi,j(k) <= AAi,j * Ci,j
6 | no_pumps = size(vars.x_bin.n, 2);
7 | no_segments = linprog.NO_PUMP_SEGMENTS;
8 | no_time_steps = size(vars.x_cont.hc,1);
9 | no_sides = 3; % Sides of a triangular facet
10 |
11 | A_pump_domain = zeros(no_time_steps * no_pumps * no_segments * no_sides, ...
12 | var_struct_length(vars));
13 | b_pump_domain = zeros(no_sides, no_time_steps, no_pumps, no_segments);
14 |
15 | % TODO: Adapt code to process pumps from multiple pump groups
16 | % Currently, only one pump group is supported
17 | lin_pump_model = lin_pumps{1};
18 |
19 | row_counter = 1;
20 | for i = 1:no_segments
21 | constraints = lin_pump_model(i).constraints;
22 | for j = 1:no_pumps
23 | for k = 1:no_time_steps
24 | for l = 1:no_sides
25 | Aineq = vars;
26 | % Add inequality constraints
27 | sign = constraints{l}{2};
28 | m_q = constraints{l}{1}(1);
29 | m_s = constraints{l}{1}(2);
30 | c = constraints{l}{1}(3);
31 | if sign == '>'
32 | % Invert signs if inequality is greater than because by convention
33 | % the inequalities in our linprog formulatin are of the form '<='
34 | m_q = -m_q;
35 | m_s = -m_s;
36 | c = -c;
37 | end
38 | Aineq.x_cont.qq(i,k,j) = m_q;
39 | Aineq.x_cont.ss(i,k,j) = m_s;
40 | Aineq.x_bin.aa(i,k,j) = -c;
41 | b_pump_domain(l,k,j,i) = 0;
42 | A_pump_domain(row_counter,:) = struct_to_vector(Aineq)';
43 | row_counter = row_counter + 1;
44 | end
45 | end
46 | end
47 | end
48 | b_pump_domain = b_pump_domain(:);
49 |
50 | end
51 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/inequality_constraints/pump_equation_constraints.m:
--------------------------------------------------------------------------------
1 | function [A_pumpeq, b_pumpeq] = pump_equation_constraints(vars, linprog, ...
2 | lin_pumps)
3 | % Implements a linearized pump characteristic equation as an inequality
4 | % constraint
5 | % -(1-nj(k)) Upump <=
6 | % delta_hj(k)- SUM
7 | % <= (1-nj(k))Upump
8 | % where SUM = sum_{i=1}^{i=4}(ddi,j*ssi,j(k) + eei,j * qqi,j(k) + ffi,j * AAi,j(k))
9 | % The constraint expands to the following two <= constraints
10 | % LHS:
11 | % - delta_hj(k) + SUM + nj(k) Upump <= Upump
12 | % RHS:
13 | % + delta_hj(k) - SUM + nj(k) Upump <= Upump
14 | no_pumps = size(vars.x_bin.n, 2);
15 | no_segments = linprog.NO_PUMP_SEGMENTS;
16 | no_time_steps = size(vars.x_cont.hc,1);
17 | number_of_constraints = 2 * no_pumps * no_time_steps;
18 |
19 | A_lh = zeros(no_pumps * no_time_steps, var_struct_length(vars));
20 | A_rh = zeros(no_pumps * no_time_steps, var_struct_length(vars));
21 | b_lh = zeros(no_time_steps, no_pumps);
22 | b_rh = zeros(no_time_steps, no_pumps);
23 |
24 | % TODO: Write function for finding pumps upstream and downstream node
25 | % and their indices in the L matrix from the information stored in the
26 | % network struct and in the incidence matrices (Lf, Lc, and L)
27 | % CURRENTLY HARD CODED
28 | % hup = hc(1);
29 | % hdown = hc(2);
30 |
31 | % TODO: Adapt code to process pumps from multiple pump groups
32 | % Currently, only one pump group is supported
33 | lin_pump_model = lin_pumps{1};
34 |
35 | index_hdown = 1; % in hc
36 | index_hup = 2; % in hc
37 |
38 | %LHS inequality
39 | row_counter = 1;
40 | for j = 1:no_pumps
41 | for k = 1:no_time_steps
42 | Aineq = vars;
43 | Aineq.x_cont.hc(k, index_hup) = 1;
44 | Aineq.x_cont.hc(k, index_hdown) = -1;
45 | Aineq.x_bin.n(k,j) = linprog.Upump;
46 | % Get the linearized pump characteristics for all discretized regions
47 | for i = 1:no_segments
48 | coeffs = lin_pump_model(i).coeffs;
49 | Aineq.x_cont.qq(i,k,j) = -coeffs(1);
50 | Aineq.x_cont.ss(i,k,j) = -coeffs(2);
51 | Aineq.x_bin.aa(i,k,j) = -coeffs(3);
52 | end
53 | b_lh(k,j) = linprog.Upump;
54 | A_lh(row_counter,:) = struct_to_vector(Aineq)';
55 | row_counter = row_counter + 1;
56 | end
57 | end
58 | b_lh = tensor_to_vector(b_lh);
59 |
60 | %RHS inequality
61 | row_counter = 1;
62 | for j = 1:no_pumps
63 | for k = 1:no_time_steps
64 | Aineq = vars;
65 | Aineq.x_cont.hc(k, index_hup) = -1;
66 | Aineq.x_cont.hc(k, index_hdown) = 1;
67 | Aineq.x_bin.n(k,j) = linprog.Upump;
68 | % Get the linearized pump characteristics for all discretized regions
69 | for i = 1:no_segments
70 | coeffs = lin_pump_model(i).coeffs;
71 | Aineq.x_cont.qq(i,k,j) = coeffs(1);
72 | Aineq.x_cont.ss(i,k,j) = coeffs(2);
73 | Aineq.x_bin.aa(i,k,j) = coeffs(3);
74 | end
75 | b_rh(k,j) = linprog.Upump;
76 | A_rh(row_counter,:) = struct_to_vector(Aineq)';
77 | row_counter = row_counter + 1;
78 | end
79 | end
80 | b_rh = tensor_to_vector(b_rh);
81 |
82 | A_pumpeq = [A_lh; A_rh];
83 | b_pumpeq = [b_lh; b_rh];
84 | end
85 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/inequality_constraints/pump_symmetry_breaking.m:
--------------------------------------------------------------------------------
1 | function [A_pumpsymmetry, b_pumpsymmetry] = pump_symmetry_breaking(vars)
2 | % Implements a symmetry breaking constraint prioritizing pumps of equal characteristics
3 | % in pump groups
4 | %
5 | % n(j)(k) >= n(j+1)(k) for j = 1 to npumps-1
6 | % wich translates into a sentence: the status of preceding pump cannot be lower then
7 | % the status of subsequent pumps. E.g. in case of two pumps, status combinations
8 | % (0,0), (1,0), and (1,1) are permitted whilst status combination (0,1) is prohibited.
9 |
10 | % The constraint is written as follows as a single-sided inequality
11 | % n(j+1)(k) - n(j)(k) <= 0, for j = 1 to npumps - 1
12 |
13 | % TODO: Generalize into multiple pump groups
14 | no_pumps = size(vars.x_bin.n, 2);
15 | no_time_steps = size(vars.x_bin.n,1);
16 | number_of_constraints = (no_pumps - 1) * no_time_steps;
17 |
18 | A_lh = zeros(number_of_constraints, var_struct_length(vars));
19 | b_lh = zeros(no_time_steps, no_pumps - 1);
20 |
21 | %LHS inequality
22 | row_counter = 1;
23 | for j = 1:(no_pumps - 1)
24 | for k = 1:no_time_steps
25 | Aineq = vars;
26 | Aineq.x_bin.n(k,j) = -1;
27 | Aineq.x_bin.n(k,j+1) = +1;
28 | b_lh(k,j) = 0;
29 | A_lh(row_counter,:) = struct_to_vector(Aineq)';
30 | row_counter = row_counter + 1;
31 | end
32 | end
33 | b_lh = tensor_to_vector(b_lh);
34 | A_pumpsymmetry = A_lh;
35 | b_pumpsymmetry = b_lh;
36 | end
37 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/inequality_constraints/q_box_constraints.m:
--------------------------------------------------------------------------------
1 | function [A_q_box, b_q_box] = q_box_constraints(vars, pump_groups, linprog)
2 | % Binary linearize pump flow q
3 | % 0 <= qqij(k) <= AAij(k) * qint,jmax
4 | number_pumps = size(vars.x_bin.n, 2);
5 | number_time_steps = size(vars.x_bin.n, 1);
6 |
7 | A_lh = zeros(number_time_steps*number_pumps, var_struct_length(vars));
8 | A_rh = zeros(number_time_steps*number_pumps, var_struct_length(vars));
9 | b_lh = zeros(number_time_steps, number_pumps);
10 | b_rh = zeros(number_time_steps, number_pumps);
11 |
12 | %LHS inequality
13 | row_counter = 1;
14 | for j = 1:number_pumps
15 | qmin = 0;
16 | for k = 1:number_time_steps
17 | Aineq = vars;
18 | Aineq.x_cont.q(k,j) = -1;
19 | b_lh(k,j) = qmin;
20 | A_lh(row_counter,:) = struct_to_vector(Aineq)';
21 | row_counter = row_counter + 1;
22 | end
23 | end
24 | b_lh = tensor_to_vector(b_lh);
25 |
26 | %RHS inequality
27 | row_counter = 1;
28 | for j = 1:number_pumps
29 | % TODO: Modify this to consider multiple pump groups
30 | qmax = pump_groups(1).pump.qint_smax;
31 | for k = 1:number_time_steps
32 | Aineq = vars;
33 | Aineq.x_cont.q(k,j) = 1;
34 | Aineq.x_bin.n(k,j) = -qmax;
35 | b_rh(k,j) = 0;
36 | A_rh(row_counter,:) = struct_to_vector(Aineq)';
37 | row_counter = row_counter + 1;
38 | end
39 | end
40 | b_rh = tensor_to_vector(b_rh);
41 |
42 | A_q_box = [A_lh; A_rh];
43 | b_q_box = [b_lh; b_rh];
44 |
45 | end
46 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/inequality_constraints/qq_box_constraints.m:
--------------------------------------------------------------------------------
1 | function [A_qq_box, b_qq_box] = qq_box_constraints(vars, pump_groups, linprog)
2 | % Binary linearize pump flow q
3 | % 0 <= qqij(k) <= AAij(k) * qint,jmax
4 | number_pumps = size(vars.x_bin.n, 2);
5 | number_time_steps = size(vars.x_bin.n, 1);
6 |
7 | A_lh = zeros(number_time_steps*number_pumps*linprog.NO_PUMP_SEGMENTS, var_struct_length(vars));
8 | A_rh = zeros(number_time_steps*number_pumps*linprog.NO_PUMP_SEGMENTS, var_struct_length(vars));
9 | b_lh = zeros(number_time_steps, number_pumps, linprog.NO_PUMP_SEGMENTS);
10 | b_rh = zeros(number_time_steps, number_pumps, linprog.NO_PUMP_SEGMENTS);
11 |
12 | %LHS inequality
13 | row_counter = 1;
14 | for l = 1:linprog.NO_PUMP_SEGMENTS
15 | for j = 1:number_pumps
16 | qmin = 0;
17 | for k = 1:number_time_steps
18 | Aineq = vars;
19 | Aineq.x_cont.qq(l,k,j) = -1;
20 | %Aineq.x_bin.aa(l,k,j)
21 | b_lh(k,j, l) = qmin;
22 | A_lh(row_counter,:) = struct_to_vector(Aineq)';
23 | row_counter = row_counter + 1;
24 | end
25 | end
26 | end
27 | b_lh = b_lh(:);
28 |
29 | %RHS inequality
30 | row_counter = 1;
31 | for l = 1:linprog.NO_PUMP_SEGMENTS
32 | for j = 1:number_pumps
33 | % TODO: Modify this to consider multiple pump groups
34 | qmax = pump_groups(1).pump.qint_smax;
35 | for k = 1:number_time_steps
36 | Aineq = vars;
37 | Aineq.x_cont.qq(l,k,j) = 1;
38 | Aineq.x_bin.aa(l,k,j) = -qmax;
39 | b_rh(k,j,l) = 0;
40 | A_rh(row_counter,:) = struct_to_vector(Aineq)';
41 | row_counter = row_counter + 1;
42 | end
43 | end
44 | end
45 | b_rh = b_rh(:);
46 |
47 | A_qq_box = [A_lh; A_rh];
48 | b_qq_box = [b_lh; b_rh];
49 |
50 | end
51 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/inequality_constraints/s_box_constraints.m:
--------------------------------------------------------------------------------
1 | function [A_s_box, b_s_box] = s_box_constraints(vars, pump_groups, linprog)
2 | % Binary linearize pump speed s
3 | % nj(k) * smin,j <= sj(k) <= nj(k) * smax,j
4 | number_pumps = size(vars.x_bin.n, 2);
5 | number_time_steps = size(vars.x_bin.n, 1);
6 |
7 | A_lh = zeros(number_time_steps*number_pumps, var_struct_length(vars));
8 | A_rh = zeros(number_time_steps*number_pumps, var_struct_length(vars));
9 | b_lh = zeros(number_time_steps, number_pumps);
10 | b_rh = zeros(number_time_steps, number_pumps);
11 |
12 | %LHS inequality
13 | row_counter = 1;
14 | for j = 1:number_pumps
15 | % TODO: Modify this to consider multiple pump groups
16 | smin = pump_groups(1).pump.smin;
17 | for k = 1:number_time_steps
18 | Aineq = vars;
19 | Aineq.x_cont.s(k,j) = -1;
20 | Aineq.x_bin.n(k,j) = smin;
21 | b_lh(k,j) = 0;
22 | A_lh(row_counter,:) = struct_to_vector(Aineq)';
23 | row_counter = row_counter + 1;
24 | end
25 | end
26 | b_lh = tensor_to_vector(b_lh);
27 |
28 | %RHS inequality
29 | row_counter = 1;
30 | for j = 1:number_pumps
31 | % TODO: Modify this to consider multiple pump groups
32 | smax = pump_groups(1).pump.smax;
33 | for k = 1:number_time_steps
34 | Aineq = vars;
35 | Aineq.x_cont.s(k,j) = 1;
36 | Aineq.x_bin.n(k,j) = -smax;
37 | b_rh(k,j) = 0;
38 | A_rh(row_counter,:) = struct_to_vector(Aineq)';
39 | row_counter = row_counter + 1;
40 | end
41 | end
42 | b_rh = tensor_to_vector(b_rh);
43 |
44 | A_s_box = [A_lh; A_rh];
45 | b_s_box = [b_lh; b_rh];
46 |
47 | end
48 |
49 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/inequality_constraints/ss_box_constraints.m:
--------------------------------------------------------------------------------
1 | function [A_ss_box, b_ss_box] = ss_box_constraints(vars, pump_groups, linprog)
2 | % Binary linearize pump speed s
3 | % AAj,i (k) sj,min <= ssj,i (k) <= AAj,i (k) sj,max
4 | number_pumps = size(vars.x_bin.n, 2);
5 | number_time_steps = size(vars.x_bin.n, 1);
6 |
7 | A_lh = zeros(number_time_steps*number_pumps*linprog.NO_PUMP_SEGMENTS, var_struct_length(vars));
8 | A_rh = zeros(number_time_steps*number_pumps*linprog.NO_PUMP_SEGMENTS, var_struct_length(vars));
9 | b_lh = zeros(number_time_steps, number_pumps,linprog.NO_PUMP_SEGMENTS);
10 | b_rh = zeros(number_time_steps, number_pumps,linprog.NO_PUMP_SEGMENTS);
11 |
12 | %LHS inequality
13 | row_counter = 1;
14 | for l = 1:linprog.NO_PUMP_SEGMENTS
15 | for j = 1:number_pumps
16 | % TODO: Modify this to consider multiple pump groups
17 | smin = pump_groups(1).pump.smin;
18 | for k = 1:number_time_steps
19 | Aineq = vars;
20 | Aineq.x_cont.ss(l,k,j) = -1;
21 | Aineq.x_bin.aa(l,k,j) = smin;
22 | b_lh(k,j,l) = 0;
23 | A_lh(row_counter,:) = struct_to_vector(Aineq)';
24 | row_counter = row_counter + 1;
25 | end
26 | end
27 | end
28 | b_lh = b_lh(:);
29 |
30 | %RHS inequality
31 | row_counter = 1;
32 | for l = 1:linprog.NO_PUMP_SEGMENTS
33 | for j = 1:number_pumps
34 | % TODO: Modify this to consider multiple pump groups
35 | smax = pump_groups(1).pump.smax;
36 | for k = 1:number_time_steps
37 | Aineq = vars;
38 | Aineq.x_cont.ss(l,k,j) = 1;
39 | Aineq.x_bin.aa(l,k,j) = -smax;
40 | b_rh(k,j,l) = 0;
41 | A_rh(row_counter,:) = struct_to_vector(Aineq)';
42 | row_counter = row_counter + 1;
43 | end
44 | end
45 | end
46 | b_rh = b_rh(:);
47 |
48 | A_ss_box = [A_lh; A_rh];
49 | b_ss_box = [b_lh; b_rh];
50 |
51 | end
52 |
53 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/lib/apply_constraint.m:
--------------------------------------------------------------------------------
1 | function out = apply_constraint(tensor, constraint_value)
2 | % Takes an n-dimensional tensor, creates a copy and sets all
3 | % values in the copy to the value provided in the
4 | % constraint_value argument
5 | out = ones(size(tensor))*constraint_value;
6 | end
7 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/lib/constant_bounds.m:
--------------------------------------------------------------------------------
1 | function [lb, ub] = constant_bounds(vector_length, lower_bound, upper_bound)
2 | % Set constant lower and upper bounds on a vector of length = vector_length
3 | lb = ones(vector_length, 1) * lower_bound;
4 | ub = ones(vector_length, 1) * upper_bound;
5 | end
6 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/lib/create_stacked_triangles.m:
--------------------------------------------------------------------------------
1 | function y = create_stacked_triangles(...
2 | p_1, p_2, p_3, p_4, p_14, p_23, inequality_signs)
3 |
4 | % Divide the area into four stacked triangles created by the four points
5 | % defining the outline of the domain and the two points lying on the boundary
6 | % of the plane along the direction of maximum curvature
7 |
8 | % Args:
9 | % Four base points in 3D - p_1, p_2, p_3, p_4
10 | % p_14 - Apex point lying on the curve belonging to the surface and
11 | % connecting points p_1 and p_4
12 | % p_23 - Apex point lying on the curve belonging to the surface and
13 | % connecting points p_2 and p_3
14 | % inequality_signs - 4 x 3 cell of '<' and '>' signs determining the
15 | % "direction" of inequalities. The inequalities are in the order
16 | % defined by the order of triangle vertices, e,g,
17 | % Face1: p_1, p_2, p_23; Face2: p_1, p_23, p_14, etc.
18 | % In each face the order of inequality follows the direction (order) of
19 | % vertices - e.g. for Face1 (1) p_1 - p_2, (2) p_2 - p_23, (3) p_23 - p_1
20 | %
21 | % Return:
22 | % 4x1 array of structures with coeff and constraints fields
23 | % coeff is z 3x1 vector of plane equation coefficients
24 | % constraints is a cell of cells with a 1x2 vector of line equation
25 | % coefficients and a string representing the direction of the inequality
26 |
27 | % TODO
28 | % Add checks that the two points picked lie on the edge of the surface
29 |
30 | % by convention the points are listed for each triangle counter-clockwise
31 | triangle_vertices = {...
32 | p_1, p_2, p_23; ...
33 | p_1, p_23, p_14; ...
34 | p_14, p_23, p_3; ...
35 | p_14, p_3, p_4};
36 | NO_TRIANGLES = size(triangle_vertices,1);
37 | NO_VERTICES = size(triangle_vertices, 2);
38 |
39 | % Project the vertices on the plane z = 0
40 | triangle_vertices_2D = cell(size(triangle_vertices));
41 | for triangle = 1:size(triangle_vertices, 1)
42 | for vertex = 1:size(triangle_vertices, 2)
43 | triangle_vertices_2D{triangle, vertex} = rem_z_coordinate(...
44 | triangle_vertices{triangle, vertex});
45 | end
46 | end
47 |
48 | % Find equations of each side of the stacked triangle
49 | for i = 1:NO_TRIANGLES
50 | p1 = triangle_vertices{i,1};
51 | p2 = triangle_vertices{i,2};
52 | p3 = triangle_vertices{i,3};
53 | [m_x1, m_x2, c] = get_plane(p1,p2,p3);
54 | y(i).coeffs = [m_x1, m_x2, c];
55 | end
56 |
57 | % Iterate through every triangle and obtain constraints for each triangles domain
58 | for i = 1:NO_TRIANGLES
59 | vertices = triangle_vertices_2D{i,:};
60 | for j = 1:NO_VERTICES
61 | p1 = vertices{j};
62 | if j". Cell of size no_of_vertices x 1
15 | %
16 | % Return:
17 | % 1x1 array of structures with coeff and constraints fields
18 | % coeff is z 3x1 vector of plane equation coefficients
19 | % constraints is a cell of cells with a 1x2 vector of line equation
20 | % coefficients and a string representing the direction of the inequality
21 |
22 | % If domain vertices are in 3D, cast them to 2D by removing z coordinate
23 | for i = 1:numel(domain_vertices)
24 | vertex = domain_vertices{i};
25 | if length(vertex) == 3
26 | domain_vertices{i} = rem_z_coordinate(vertex);
27 | end
28 | end
29 |
30 | c_coeff = p_t(3); % Value at the point at which the tangent had been derived
31 | % Convert the equation from deviation variables dx, dy to absolute values, x
32 | % and y
33 | c_coeff = c_coeff - x_tan * p_t(1) - y_tan * p_t(2);
34 |
35 | y(1).coeff = [x_tan, y_tan, c_coeff];
36 |
37 | number_vertices = numel(domain_vertices);
38 | for i = 1:number_vertices
39 | p_1 = domain_vertices{i};
40 | if i' signs determining the
9 | % "direction" of inequalities. The inequalities are in the order
10 | % defined by the order of base points, e,g,
11 | % Face1: p_1, p_2, p_a; Face2: p_2, p_3, p_a, etc.
12 | % In each face the order of inequality lines is: (e.g. for Face1
13 | % (1) p_1 - p_2, (2) p_1 - p_a, (3) p_2 - p_a
14 | % Return:
15 | % 4x1 array of structures with coeff and constraints fields
16 | % coeff is z 3x1 vector of plane equation coefficients
17 | % constraints is a cell of cells with a 1x2 vector of line equation
18 | % coefficients and a string representing the direction of the inequality
19 |
20 |
21 | triangle_vertices = {...
22 | p_1,p_a,p_2; ...
23 | p_2,p_a,p_3; ...
24 | p_3,p_a,p_4; ...
25 | p_4,p_a,p_1};
26 | NO_TRIANGLES = size(triangle_vertices,1);
27 | NO_VERTICES = size(triangle_vertices, 2);
28 |
29 | % Project the vertices on the plane z = 0
30 | triangle_vertices_2D = cell(size(triangle_vertices));
31 | for triangle = 1:size(triangle_vertices, 1)
32 | for vertex = 1:size(triangle_vertices, 2)
33 | triangle_vertices_2D{triangle, vertex} = rem_z_coordinate(...
34 | triangle_vertices{triangle, vertex});
35 | end
36 | end
37 |
38 | % Find equations of each side of the sides of the tetrahedron
39 | for i = 1:NO_TRIANGLES
40 | p1 = triangle_vertices{i,1};
41 | p2 = triangle_vertices{i,2};
42 | p3 = triangle_vertices{i,3};
43 | [m_x1, m_x2, c] = get_plane(p1,p2,p3);
44 | y(i).coeffs(1) = m_x1;
45 | y(i).coeffs(2) = m_x2;
46 | y(i).coeffs(3) = c;
47 | end
48 |
49 | % Project point onto z = 0
50 | %base_points_2D = cellfun(@rem_z_coordinate, {p_1, p_2, p_3, p_4}, ...
51 | % 'UniformOutput',false);
52 |
53 | % Iterate through every triangle and obtain constraints for each triangles domain
54 | for i = 1:NO_TRIANGLES
55 | vertices = triangle_vertices_2D(i,:);
56 | for j = 1:NO_VERTICES
57 | p1 = vertices{j};
58 | if j' or '<'.
5 | % We use convention that independent and dependent variable are lumped into
6 | % one vector x = [x(1), x(2)] where x(2) = f (x(1)), i.e y <- x(2) and x <- x(1)
7 | %
8 | % Args:
9 | % coeffs: 1 x 2 array with coefficients m and c
10 | % Return:
11 | % y: a structure storing the index of the variable for which the constraints
12 | % are applied to - 2 - dependent variable, 1 - independent variable
13 | % and the line coeficients - a 2x1 array.
14 | %
15 | % The function handles two cases
16 | % (1): Inequality boundary x(2) = m * x(1) + c can be defined. In this case
17 | % the function gives out the output y.var_index = 2, y.coeffs = coeffs
18 | % (2): Inequality boundary cannot be defined because m = Inf. In this case
19 | % y.var_index = 1, i.e. the inequality is applied to the independent variable
20 | % and y.coeffs = [0, coeffs(2)];
21 |
22 | if length(coeffs) ~= 2
23 | error('Expected coeffs arguments needs to be an array of length 2. Length %d given', ...
24 | length(coeffs));
25 | end
26 | if isnan(coeffs(0))
27 | y.var_index = 1;
28 | y.coeffs = [0, coeffs(2)];
29 | else
30 | y.var_index = 2;
31 | y.coeffs = coeffs;
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/lib/inject_vector.m:
--------------------------------------------------------------------------------
1 | function vector = inject_vector(recipient_vec, injection_vec, start_position)
2 | % Take the injection vector and place it into the recipient vector at a
3 | % position (index) given in argument `start_position'
4 |
5 | % * * * * * * (recipient vector)
6 | % ^
7 | % | injection position
8 | % |
9 | % x x x (injection vector)
10 | % produces
11 | % * * x x x * (result)
12 |
13 | length_recipient = length(recipient_vec);
14 | length_injection = length(injection_vec);
15 | end_position = start_position + length_injection - 1;
16 | % Check if the injection vector will fit into the recipient vector
17 | if end_position > length_recipient
18 | difference = end_position - length_recipient;
19 | max_length = length_injection - difference;
20 | error('Injected vector of length %d too long. Max. Length = %d\n',...
21 | length_injection, max_length);
22 | end
23 | vector = recipient_vec;
24 | vector(start_position:end_position)=injection_vec;
25 | end
26 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/lib/pump_power_tangent.m:
--------------------------------------------------------------------------------
1 | function out = pump_power_tangent(pump, q_op, s_op)
2 | % Find tangents in the `q` and `s` directions and the operating point in the
3 | % (q, s, P) coordinates
4 | % The linear power equation coefficients are the coefficients of a linear
5 | % power consumption model:
6 | % P_lin = m_dq * q + m_ds * s + c where
7 | % c = P(q_op, s_op) - m_dq * q_op - m_ds * s_op
8 | % Tangent in the x direction (flow)
9 | out.m_dq = 3*pump.ep*q_op^2 + 2*pump.fp*q_op*s_op + pump.gp*s_op^2;
10 | % Tangent in the y direction (speed)
11 | out.m_ds = pump.fp*q_op^2 + 2*pump.gp*q_op*s_op + 3*pump.hp*s_op^2;
12 | % Get the tangent point
13 | out.p_t = [q_op, s_op, pump_power_consumption(pump, q_op, s_op, 1)];
14 | end
15 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/lib/tensor_to_vector.m:
--------------------------------------------------------------------------------
1 | function vec = tensor_to_vector(tensor)
2 | % Unstack the multidimensional tensor into a vector
3 | num_elements = numel(tensor);
4 | vec = reshape(tensor, num_elements, 1);
5 | end
6 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/lib/var_struct_length.m:
--------------------------------------------------------------------------------
1 | function struct_length = var_struct_length(var_struct, var_type)
2 | % Find the total length of all vectors of the MILP var structure
3 | % Args:
4 | % var_struct: var structure containing fields x_cont and x_bin
5 | % var_type (Optional): specifies the type of variable which
6 | % length is to be calculated.
7 | %
8 |
9 | if nargin == 2
10 | parsed_fields = {var_type};
11 | else
12 | parsed_fields = {'x_cont', 'x_bin'};
13 | end
14 |
15 | field_names = fieldnames(var_struct);
16 | struct_length = 0;
17 | for k=1:numel(field_names)
18 | field_name = field_names{k};
19 | % Iterate only through fields that contain continuous or binary vars
20 | if ismember(field_name, parsed_fields)
21 | subfield_names = fieldnames(var_struct.(field_name));
22 | for l=1:numel(subfield_names)
23 | subfield_name = subfield_names{l};
24 | field_length = numel(var_struct.(field_name).(subfield_name));
25 | struct_length = struct_length + field_length;
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/linearize_pipe_characteristic.m:
--------------------------------------------------------------------------------
1 | function out = linearize_pipe_characteristic(R, q_op, Upipe)
2 | % Divide pipe characteristics into linear segments
3 | % Args:
4 | % R - pipe resistance
5 | % q_op - pipe operating point (sorf of nominal flow). Could be set from
6 | % simulation e.g. as the flow at 12 o'clock, for instance.
7 | % Upipe - a large number representing the maximum possible flow
8 | % that can flow through the pipe
9 | % Returns:
10 | % out: struct array with fields: coeffs and limits
11 | % coeffs is a struct with coefficients m and c of each linear segment
12 | % defined with equation dh = m * q + c
13 | % limits is a cell containing two 1x2 vectors specifying the start and end
14 | % points of each segment in the (q, dh) plane.
15 | %
16 |
17 | % TODO: GENERALIZE TO MORE THAN THREE SEGMENTS
18 | N_SEGMENTS = 3;
19 |
20 | % Find the head drop for the operating point and Upipe
21 | dh_op = pipe_characteristic(R, q_op);
22 | dh_Upipe = pipe_characteristic(R, Upipe);
23 | % We exploit pipe characteristic symmetry around point (0,0)
24 | limit_points = {[-Upipe, -dh_Upipe], [-q_op, -dh_op];...
25 | [-q_op, -dh_op], [q_op, dh_op];...
26 | [q_op, dh_op], [Upipe, dh_Upipe]};
27 | % Populate the `out` variable with data
28 | for i = 1:N_SEGMENTS
29 | % Coefficients of the line equations
30 | [out(i).coeffs.m, out(i).coeffs.c] = get_line(...
31 | limit_points{i,1}, limit_points{i, 2});
32 | % Limits of each segment
33 | out(i).limits = {limit_points{i,1}, limit_points{i, 2}};
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/linearize_pipes.m:
--------------------------------------------------------------------------------
1 | function out = linearize_pipes(network, sim_out, rep_hr, Upipes)
2 | % Linearizes all pipes in the network given in the network structure
3 | % using the linearization points obtained from the outputs of the simulator.
4 | % Requires prior initialization of the network followed by a simulation
5 | % run over the planning time horizon.
6 | %
7 | % Args:
8 | % network - network structure
9 | % sim_out - simulation output structure
10 | % rep_hr - representative hour (1 - 24)
11 | % Upipe - max flow(s) for linearization - one per pipe
12 | % Return:
13 | % Cell of linearized pipe output structures
14 |
15 | no_pipes = length(network.Rs);
16 | out = cell(no_pipes, 1);
17 | if length(Upipes) == 1
18 | Upipes = repmat(Upipes, no_pipes, 1);
19 | else
20 | if length(Upipes) ~= no_pipes
21 | error('Length of vector Upipes: %d not equal to no. of pipes: %d', length(Upipes), no_pipes);
22 | end
23 | end
24 | q_sim = sim_out.q(network.pipe_indices,:); % Select flows in pipes only
25 |
26 | for i = 1:no_pipes
27 | R = network.Rs(i);
28 | Upipe = Upipes(i);
29 | q_op = abs(q_sim(i, rep_hr));
30 | out{i} = linearize_pipe_characteristic(R, q_op, Upipe);
31 | end
32 | end
33 |
34 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/linearize_power_model.m:
--------------------------------------------------------------------------------
1 | function y = linearize_power_model(pump, q_op, s_op, domain_vertices,...
2 | constraint_signs)
3 | % Linearization of the power consumption model given in function
4 | % pump_power_consumption.m`
5 | % The linearized model is applied to a single pump, hence the nonlinear
6 | % power consumption for a group of equal pumps working at the same speed s
7 | % is linearized under assumption n = 1.
8 |
9 | % Args:
10 | % pump - structure with pump data
11 | % q_op - operating point pump flow, L/s
12 | % s_op - operting point pump speed, -
13 | % Return:
14 | % 1x1 vector of structures with coeff and constraints fields
15 | % coeff is z 3x1 vector of linear power equation coefficients
16 | % constraints is a cell of cells with a 1x2 vector of line equation
17 | % coefficients and a string representing the direction of the inequality
18 |
19 | %% Currently only supports the TANGENT surface model
20 | % TODO: ALLOW DIFFERENT LINEARIZATION METHODS ON THE POWER MODEL
21 | tangent = pump_power_tangent(pump, q_op, s_op);
22 | p_t = tangent.p_t;
23 | m_dq = tangent.m_dq;
24 | m_ds = tangent.m_ds;
25 | % Get the tangent surface (linearized) power consumption model
26 | y = create_tangent_surface(p_t, m_dq, m_ds, domain_vertices, constraint_signs);
27 | end
28 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/linearize_power_pumps.m:
--------------------------------------------------------------------------------
1 | function out = linearize_power_pumps(pump_groups, q_op, s_op)
2 | % Linearizes power comsumption for all pump (groups) in the network
3 | % Since all pumps in every pump group are assumed to be equal, linearization is
4 | % performed for a (representative) pump of each pump group.
5 | % Args:
6 | % pump_groups - vector of structs of (representative) pumps for each pump group
7 | % q_op, s_op = lists of operating points (q_op and s_op) - one per each group
8 | % Return:
9 | % Cell of linearized pipe output structures
10 | number_pump_groups = length(pump_groups);
11 |
12 | if length(q_op) ~= length(s_op)
13 | error("Vectors of q and s coordinates of operating points not equal");
14 | end
15 |
16 | if length(q_op) == 1
17 | q_op = repmat(q_op, number_pump_groups, 1);
18 | s_op = repmat(s_op, number_pump_groups, 1);
19 | end
20 |
21 | if length(q_op) ~= number_pump_groups
22 | error("Number of operating points not equal to the number of pump groups");
23 | end
24 |
25 | out = cell(length(pump_groups));
26 | for i = 1:length(out)
27 | out{i} = pump_power_tangent(pump_groups(i).pump, q_op(i), s_op(i));
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/linearize_pump_characteristic.m:
--------------------------------------------------------------------------------
1 | function out = linearize_pump_characteristic(pump)
2 | % Linearize a nonlinear hydraulic pump characteristic over a speed-flow domain
3 | % defined by boundary points p1, p2, p3, and p4 that are calculated from
4 | % information contained in the pump structure.
5 | % Args:
6 | % pump - structure with pump data
7 | % Return:
8 | % array of structures with plane coefficients
9 |
10 | % Each domain is defined by polynomial coefficients a, b, c
11 | % describing plane equation z = ax + by + c
12 |
13 | %% Get the nominal point
14 | [qn, sn, Hn] = pump_nominal_point(pump);
15 | pn = [qn, sn, Hn];
16 |
17 | %% Define the boundary point coordinates
18 | q_min = 0;
19 | s_min = pump.smin;
20 | s_max = pump.smax;
21 | % Find intercept flows for minimum and maximum speeds respectively
22 | q_int_smin = pump_intercept_flow(pump, 1, s_min);
23 | q_int_smax = pump_intercept_flow(pump, 1, s_max);
24 | % Define the boundary points
25 | % Note: 1 in the function call specifies that pump head and pump intercept
26 | % flow are calculated for a single pump
27 | p1 = [q_min, s_min, pump_head(pump, q_min, 1, s_min)]; % min-min
28 | p2 = [q_min, s_max, pump_head(pump, q_min, 1, s_max)]; % min-max
29 | p3 = [q_int_smax , s_max, pump_head(pump, q_int_smax, 1, s_max)]; % max-max
30 | p4 = [q_int_smin , s_min, pump_head(pump, q_int_smin, 1, s_min)]; % max-min
31 |
32 | % Specify a cell with constraint signs
33 | constraint_signs = {'>', '<', '>';...
34 | '>', '>', '<';...
35 | '<', '>', '>';...
36 | '<', '<', '>'};
37 | domain_vertices = {p1, p2, p3, p4};
38 |
39 | %%%%%%%%%%%%%%%%%%%
40 | % s ^
41 | % | 3. (qintmax, smax)
42 | % | x---------------------------x
43 | % | 2 (qmin, smax) | (2) /
44 | % | | /
45 | % | | /
46 | % | | (1) o (3) /
47 | % | | pn /
48 | % | | ^ \ /
49 | % | | / (4) v /
50 | % | | <-- /
51 | % | x------------------x
52 | % | 1 (qmin,smin) 4 (qintmin, smin)
53 | % |________________________________________________> q
54 |
55 | % The domains on plane z = 0 are
56 | % In each subdomain the lines are calculated in order of:
57 | % vertex1 - pn
58 | % pn - vertex2
59 | % vertex2 - vertex1
60 | % as illustrated in the diagram above
61 |
62 | %% Currently only supports the tetrahedron linearization in create_tetrahedron
63 | out = create_tetrahedron(p1, p2, p3, p4, pn, constraint_signs);
64 | end
65 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/linearize_pumps.m:
--------------------------------------------------------------------------------
1 | function out = linearize_pumps(pump_groups)
2 | % Linearizes all pump (groups) in the network given in the network structure
3 | % Since all pumps in a pump group are assumed to be equal, linearization is
4 | % performed for a (representative) pump of each pump group.
5 | % Args:
6 | % network - network structure
7 | % Return:
8 | % Cell of linearized pipe output structures
9 |
10 | out = cell(size(pump_groups));
11 | for i = 1:length(out)
12 | out{i} = linearize_pump_characteristic(pump_groups(i).pump);
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/pump_head_linear.m:
--------------------------------------------------------------------------------
1 | function h = pump_head_linear(linearized_model, q, s, domain_number)
2 | % Find head of a linearized pump model operating at speed s
3 | % and flow q.
4 | % The linearized pump model contains four planes and is derived using
5 | % function `linearize_pump_characteristic`.
6 | % Flow denotes the flow passing through a (single) pump, as opposed to
7 | % the nonlinear equation where the flow variable denotes the flow going
8 | % through the entire pump group of n equal pumps
9 | model = linearized_model(domain_number);
10 | h = model.coeffs(1) * q + model.coeffs(2) * s + model.coeffs(3);
11 | end
12 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/pump_power_linear.m:
--------------------------------------------------------------------------------
1 | function y = pump_power_linear(linearized_model, q, s)
2 | % Currently assumes only the tangent power consumption model
3 | % TODO: FINISH
4 | end
5 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/set_A_b_matrices.m:
--------------------------------------------------------------------------------
1 | function [A, b] = set_A_b_matrices(vars, linprog, lin_power, lin_pipes, ...
2 | lin_pumps, pump_groups, sparse_out)
3 | % Calculate all inequality constraints and create A and b matrices
4 | [A_p, b_p] = power_ineq_constraint(vars, linprog);
5 | [A_p_lin, b_p_lin] = power_model_ineq_constraint(vars, lin_power, linprog);
6 | [A_pipe, b_pipe] = pipe_flow_segment_constraints(vars, lin_pipes, linprog);
7 | %[A_s_box, b_s_box] = s_box_constraints(vars, pump_groups, linprog);
8 | %[A_q_box, b_q_box] = q_box_constraints(vars, pump_groups, linprog);
9 | [A_ss_box, b_ss_box] = ss_box_constraints(vars, pump_groups, linprog);
10 | [A_qq_box, b_qq_box] = qq_box_constraints(vars, pump_groups, linprog);
11 | [A_pumpeq, b_pumpeq] = pump_equation_constraints(vars, linprog, lin_pumps);
12 | [A_pump_domain, b_pump_domain] = pump_domain_constraints(vars, linprog, ...
13 | lin_pumps);
14 | [A_pumpsymmetry, b_pumpsymmetry] = pump_symmetry_breaking(vars);
15 |
16 | A = [A_p; A_p_lin; A_pipe; ... %A_s_box; A_q_box;
17 | A_ss_box; A_qq_box; A_pumpeq; A_pump_domain; A_pumpsymmetry];
18 | b = [b_p; b_p_lin; b_pipe; ... %b_s_box; b_q_box;
19 | b_ss_box; b_qq_box; b_pumpeq; b_pump_domain; b_pumpsymmetry];
20 |
21 |
22 | if (sparse_out == true)
23 | A = sparse(A);
24 | b = sparse(b);
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/set_Aeq_beq_matrices.m:
--------------------------------------------------------------------------------
1 | function [Aeq, beq] = set_Aeq_beq_matrices(vars, network, sim_input, lin_pipes, ...
2 | sparse_out)
3 | % Calculate all equality constraints and create Aeq and beq matrices
4 | [Aeq_ht, beq_ht] = ht_constraints(vars, network);
5 | [Aeq_qpipe, beq_qpipe] = ww_constraints(vars, network);
6 | [Aeq_bb, beq_bb] = bb_constraints(vars);
7 | [Aeq_dhpipe, beq_dhpipe] = pipe_headloss_constraints(vars, network, ...
8 | sim_input, lin_pipes);
9 | [Aeq_ss, beq_ss] = ss_constraints(vars);
10 | [Aeq_qq, beq_qq] = qq_constraints(vars);
11 | [Aeq_aa, beq_aa] = aa_constraints(vars);
12 | [Aeq_nodeq, beq_nodeq] = nodeq_constraints(vars, network, sim_input);
13 | [Aeq_pumpgroup, beq_pumpgroup] = pumpgroup_constraints(vars, network);
14 |
15 | % Concatenate all Aeq matrices and beq vectors
16 | Aeq = [Aeq_ht; Aeq_qpipe; Aeq_bb; Aeq_dhpipe; Aeq_ss; Aeq_qq; Aeq_aa; ...
17 | Aeq_nodeq; Aeq_pumpgroup];
18 | beq = [beq_ht; beq_qpipe; beq_bb; beq_dhpipe; beq_ss; beq_qq; beq_aa; ...
19 | beq_nodeq; beq_pumpgroup];
20 |
21 | if (sparse_out == true)
22 | Aeq = sparse(Aeq);
23 | beq = sparse(beq);
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/set_intcon_vector.m:
--------------------------------------------------------------------------------
1 | function intcon = set_intcon_vector(var_struct)
2 | % Set intcon vector for the MILP programme formulation.
3 | % The vector is composed of indices of variables that are binary.
4 |
5 | % Args:
6 | % var_struct - structure of variables with x_cont and x_bin fields
7 | % storing continuous variables and integer variables, respectively
8 |
9 | intcon = find([zeros(var_struct_length(var_struct, 'x_cont'), 1); ...
10 | ones(var_struct_length(var_struct, 'x_bin'), 1)]);
11 | end
12 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/set_objective_vector.m:
--------------------------------------------------------------------------------
1 | function f_vector = set_objective_vector(vars, network, input, linprog, ...
2 | sparse_out)
3 | % Find a vector of objective vector coefficients
4 | % Apply tariff multiplier to the power consumption vector in x_cont variable
5 | % structure
6 | % The assignment follows data structure in which power consumption is
7 | % represented as a vector of power calculated in every pump over calculation
8 | % time horizon.
9 |
10 | % WARNING: no_steps needs to be corresponding to the number of steps chosen
11 | % during initialisation of the vars structure. Potential source of conflict
12 | % TODO: Move no_steps to a common data structure, e.g. as a field in the
13 | % network structure, to avoid situation where different numbers of the
14 | % time_step variable are provided to different functions, leading to
15 | % calculation issues that can be difficult to track down.
16 | % no_steps could, alternatively, be incorporated in the var structure.
17 |
18 | % Args:
19 | % vars - structure with all continuous and binary variables in the MILP
20 | % formulation
21 | % network_data - structure with network data
22 | % input - structure with input data
23 | % linprog - structure of linear programme configuration parameters
24 | % sparse_out - logical variable defining whether to output is sparse
25 | % [optional], true by default
26 |
27 | % Returns:
28 | % vector of objectives
29 |
30 | % Create copies of variable structures used to later create an objective
31 | % coefficient vector
32 |
33 | no_steps = linprog.NO_PRED_STEPS;
34 | time_step = linprog.TIME_STEP;
35 | tariff_vec = input.tariff;
36 |
37 | if nargin < 6
38 | % If sparse_out not provided
39 | sparse_out = true;
40 | end
41 |
42 | f_struct = vars;
43 |
44 | if no_steps > length(tariff_vec)
45 | error('Number of scheduling steps greater than the length of the tariff');
46 | end
47 |
48 | for i = 1:network.npumps
49 | for j = 1:no_steps
50 | % Set objective function coefficientsm i.e. multipliers of the pump
51 | % power consumption `p`
52 | f_struct.x_cont.p(j,i) = tariff_vec(j) * time_step;
53 | end
54 | end
55 |
56 | f_vector = [struct_to_vector(f_struct, 'x_cont'); ...
57 | struct_to_vector(f_struct, 'x_bin')];
58 |
59 | % Convert the output vector to its sparse representation, if required
60 | if (sparse_out == true)
61 | f_vector = sparse(f_vector);
62 | end
63 | end
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/matlab_octave/milp_formulation/set_variable_bounds.m:
--------------------------------------------------------------------------------
1 | function [lb, ub] = set_variable_bounds(var_struct, constraints, ht_forced_end)
2 | % Set upper and lower boundaries for continuous and binary decision variables
3 | % Set constraints on continuous variables
4 | % Args:
5 | % var_struct - structure of variables with x_cont and x_bin fields
6 | % constraints -
7 |
8 | % Create copies of the var stucture. (in this way we can make sure that the
9 | % size and order of variables in the lower and upper bound vectors corresponds
10 | % to the size and order of variables in the decision variable vector x.
11 | lb_struct = var_struct;
12 | ub_struct = var_struct;
13 |
14 | parsed_fields = {'x_cont', 'x_bin'};
15 |
16 | % Iterate through all variables
17 | field_names = fieldnames(var_struct);
18 | for i = 1:length(field_names)
19 | var_type = field_names{i};
20 | if ~ismember(var_type, parsed_fields)
21 | continue
22 | end
23 | var_names = fieldnames(var_struct.(var_type));
24 | for j=1:length(var_names)
25 | var_name = var_names{j};
26 | constraint = constraints.(var_type).(var_name);
27 | % Apply constraints to tensors
28 | lb_constraint = apply_constraint(getfield(lb_struct.(var_type), var_name), ...
29 | constraint(1));
30 | ub_constraint = apply_constraint(getfield(ub_struct.(var_type), var_name), ...
31 | constraint(2));
32 |
33 | % Enforce that the first and the last value of ht are equal
34 | % TODO: Remove this if statement and restructure the constraint
35 | % application code to include single values as well as vectors
36 | % (profiles). At the moment constraints can only be formulated
37 | % as upper and lower bounds (constants). Allow code to include
38 | % constraints specified as vectors (profiles). Raised in issue
39 | % #7
40 | if strcmp(var_name,'ht')
41 | lb_constraint(end) = ht_forced_end; % Equal to network.tanks(1).ht0 = 233 (default 2p1t case study)
42 | end
43 | % Set constraint values
44 | lb_struct.(var_type).(var_name) = lb_constraint;
45 | ub_struct.(var_type).(var_name) = ub_constraint;
46 | end
47 | end
48 | lb = struct_to_vector(lb_struct);
49 | ub = struct_to_vector(ub_struct);
50 | end
51 |
--------------------------------------------------------------------------------
/src/matlab_octave/post_processing/get_element_flows.m:
--------------------------------------------------------------------------------
1 | function qel = get_element_flows(x_vector, vars, el_number)
2 | % Retrieve information about calculated element flows from decision
3 | % variable vector x
4 | length_qel = length(vars.x_cont.qel(:, el_number));
5 | qel = zeros(length_qel, 1);
6 | for i = 1:length_qel
7 | x_index = map_var_index_to_lp_vector(vars, 'qel', {i, el_number});
8 | qel(i) = x_vector(x_index);
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/src/matlab_octave/post_processing/get_heads.m:
--------------------------------------------------------------------------------
1 | function heads = get_heads(x_vector, vars, calc_node_number)
2 | % Retrieve information about calculated head levels from decision
3 | % variable vector x
4 | length_heads = length(vars.x_cont.hc(:,calc_node_number));
5 | heads = zeros(length_heads, 1);
6 | for i = 1:length_heads
7 | x_index = map_var_index_to_lp_vector(vars, 'hc', {i,calc_node_number});
8 | heads(i) = x_vector(x_index);
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/src/matlab_octave/post_processing/get_number_working_pumps.m:
--------------------------------------------------------------------------------
1 | function npump_vec = get_number_working_pumps(x_vector, vars, no_pumps)
2 | % Retrieve the number of working pumps at each time step
3 | % TODO: Generalize the function to work with multiple pump groups
4 | length_npump = size(vars.x_bin.n, 1);
5 | npump_vec = zeros(length_npump, 1);
6 | for i = 1:length_npump
7 | n_pump = 0;
8 | for j = 1:no_pumps
9 | x_index = map_var_index_to_lp_vector(vars, 'n', {i, j});
10 | n_pump = n_pump + x_vector(x_index);
11 | end
12 | npump_vec(i) = n_pump;
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/src/matlab_octave/post_processing/get_pump_flows.m:
--------------------------------------------------------------------------------
1 | function qpump_vec = get_pump_flows(x_vector, vars, no_pumps)
2 | % Retrieve pump group flows from the vector of decision variables x
3 | % TODO: Generalize the function to work with multiple pump groups
4 | length_qpump = size(vars.x_cont.q, 1);
5 | qpump_vec = zeros(length_qpump, 1);
6 | for i = 1:length_qpump
7 | q_pump = 0;
8 | for j = 1:no_pumps
9 | x_index = map_var_index_to_lp_vector(vars, 'q', {i, j});
10 | q_pump = q_pump + x_vector(x_index);
11 | end
12 | qpump_vec(i) = q_pump;
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/src/matlab_octave/post_processing/get_pump_powers.m:
--------------------------------------------------------------------------------
1 | function power_vec = get_pump_powers(x_vector, vars, no_pumps)
2 | % Get the power consumption of the pump group
3 | % TODO: Generalize the function to work with multiple pump groups
4 | length_power = size(vars.x_cont.p, 1);
5 | power_vec = zeros(length_power, 1);
6 | for i = 1:length_power
7 | p_pump = 0;
8 | pumps_on = 0;
9 | for j = 1:no_pumps
10 | x_index_p = map_var_index_to_lp_vector(vars, 'p', {i, j});
11 | x_index_n = map_var_index_to_lp_vector(vars, 'n', {i, j});
12 | n = x_vector(x_index_n);
13 | p = x_vector(x_index_p);
14 | if round(n) == 1
15 | pumps_on = pumps_on + 1;
16 | p_pump = p_pump + p;
17 | end
18 | end
19 | power_vec(i) = p_pump;
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/src/matlab_octave/post_processing/get_pump_speeds.m:
--------------------------------------------------------------------------------
1 | function spump_vec = get_pump_speeds(x_vector, vars, no_pumps)
2 | % Retrieve pump group flows from the vector of decision variables x
3 | % TODO: Generalize the function to work with multiple pump groups
4 | length_spump = size(vars.x_cont.s, 1);
5 | spump_vec = zeros(length_spump, 1);
6 | for i = 1:length_spump
7 | s_pump = 0;
8 | pumps_on = 0;
9 | for j = 1:no_pumps
10 | x_index_s = map_var_index_to_lp_vector(vars, 's', {i, j});
11 | x_index_n = map_var_index_to_lp_vector(vars, 'n', {i, j});
12 | n = x_vector(x_index_n);
13 | s = x_vector(x_index_s);
14 | if n == 1
15 | pumps_on = pumps_on + 1;
16 | s_pump = s_pump + s;
17 | end
18 | end
19 | if pumps_on == 0
20 | pumps_on = 1;
21 | end
22 | spump_vec(i) = s_pump/pumps_on;
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/src/matlab_octave/post_processing/get_tank_levels.m:
--------------------------------------------------------------------------------
1 | function levels = get_tank_levels(x_vector, vars, tank_number)
2 | % Retrieves a Nx1 vector of tank levels for a given tank
3 | % in the network, where N is the simulation/optimization
4 | % time horizon
5 | length_levels = length(vars.x_cont.ht(:,tank_number));
6 | levels = zeros(length_levels, 1);
7 | for i = 1:length_levels
8 | x_index = map_var_index_to_lp_vector(vars, 'ht', {i,tank_number});
9 | levels(i) = x_vector(x_index);
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/src/matlab_octave/post_processing/plot_optim_outputs.m:
--------------------------------------------------------------------------------
1 | function y = plot_optim_outputs(...
2 | input, x_vector, vars, optim_output, titles, folder)
3 | % Plot optimization outputs of the 2 pump 1 tank scheduling problem
4 | y = 0;
5 | time_horizon = 24;
6 | save_to_pdf = false;
7 |
8 | % Flows and demands
9 | q3 = get_element_flows(x_vector, vars, 3)';
10 | q4 = get_element_flows(x_vector, vars, 4)';
11 | q5 = get_element_flows(x_vector, vars, 5)';
12 | file_name1 = fullfile(folder, "optimized_system_flows");
13 | plot_elem_flows_demands_2p1t(time_horizon, input, q3, q4, q5, file_name1, titles{1}, save_to_pdf);
14 | % Nodal heads
15 | hc1 = get_heads(x_vector, vars, 1)';
16 | hc2 = get_heads(x_vector, vars, 2)';
17 | hc3 = get_heads(x_vector, vars, 3)';
18 | hc4 = get_heads(x_vector, vars, 4)';
19 | ht = get_tank_levels(x_vector, vars, 1)';
20 | file_name2 = fullfile(folder, "optimized_system_heads");
21 | plot_nodal_heads_2p1t(time_horizon, hc1, hc2, hc3, hc4, ht, file_name2, titles{2}, save_to_pdf);
22 | % Energy consumption
23 | file_name3 = fullfile(folder, 'optimized_energy_consumption');
24 | pump_power = get_pump_powers(x_vector, vars, 2);
25 | pump_cost = pump_power.*input.tariff';
26 | plot_energy_consumption_2p1t(time_horizon, input, pump_cost, file_name3, titles{3}, save_to_pdf);
27 | % Pump schedules
28 | file_name4 = fullfile(folder, 'optimized_schedule');
29 | n_pumps = get_number_working_pumps(x_vector, vars, 2)';
30 | %s_pumps = get_pump_speeds(x_vector, vars, 2)';
31 | s_pumps = optim_output.s;
32 | plot_linprog_pump_schedules_2p1t(time_horizon, n_pumps, s_pumps, file_name4, titles{4}, save_to_pdf);
33 | y = 1;
34 | end
35 |
--------------------------------------------------------------------------------
/src/matlab_octave/run_2p1t_with_matlab.m:
--------------------------------------------------------------------------------
1 | %% Run case study with the two pump single tank network using intlinprog
2 | % Goes through the following steps:
3 | % 1. Converts the network to a MILP formulation (requires a simulation step)
4 | % 2. Runs optimization with MATLAB's intlinprog solver
5 | % 3. Visualises the results, i.e.
6 | % (a) Simulation results with initial pump schedule
7 | % (b) Outputs of the linear programme
8 | % (c) Simulation results with optimized pump schedule
9 |
10 | %% Set flags
11 | save_to_mps = 1; % Save the linear programme to mps file
12 | save_to_mat = 1; % Save the linear programme matrices to .mat file
13 | final_water_level = 232.5; % Final water level (head) enforced in the reservoir
14 |
15 | %% Set options
16 | max_optim_time = 100; % Maximum allowed optimization time in seconds
17 |
18 | %% Specify intlinprog's parameter (options) structure
19 | options = optimoptions(...
20 | 'intlinprog', ...
21 | 'CutMaxIterations', 25, ...
22 | 'MaxTime', max_optim_time,...
23 | 'CutGeneration', 'intermediate', ...
24 | 'Heuristics','intermediate', ...
25 | 'HeuristicsMaxNodes', 100, ...
26 | 'LPOptimalityTolerance', 1e-3, ...
27 | 'BranchRule', 'strongpscost',...
28 | 'IntegerPreprocess','advanced');
29 |
30 | %% Load 2p1t network and input data
31 | [input,const,linprog,network,sim,pump_groups] = initialise_2p1t();
32 |
33 | %% Initialise continuous and binary variable structures required to construct linear programme
34 | vars = initialise_var_structure(network, linprog);
35 |
36 | %% Initial simulation to simulate flows, calculated heads, power consumption and tank levels
37 | init_sim_output = simulator_2p1t(input.init_schedule, network, input, sim);
38 |
39 | %% Transform network to a mixed integer linear model
40 | lin_model = transform_2p1t_to_milp(input, linprog, network, pump_groups, ...
41 | init_sim_output, save_to_mps, save_to_mat, 1, final_water_level);
42 |
43 | %% Set initial condition for the linear programme
44 | x0=[];
45 |
46 | %% Run mixed integer optimization in MATLAB
47 | [n, s, x] = calculate_schedule(lin_model, x0, vars, options);
48 | optim_output.n = n;
49 | optim_output.s = s;
50 | optim_output.x = x;
51 |
52 | %% Obtain schedule structure that is compatible with the simulator
53 | % NOTE: In scheduler, each pump is represented individually whilst simulator models
54 | % groups of pumps as one unit
55 | % NOTE: This only works for a single pump group
56 | [optim_n, optim_s] = linprog_to_sim_schedule(n,s);
57 | optim_output.schedule.N = optim_n;
58 | optim_output.schedule.S = optim_s;
59 |
60 | %% Run the simulator with optimized schedules
61 | optim_sim_output = simulator_2p1t(...
62 | optim_output.schedule, network, input, sim);
63 |
64 | %% Visualise results:
65 | % 1. simulation with initial schedule
66 | % 2. optimizer output
67 | % 3. final_simulation with optimized schedule
68 | plot_scheduling_2p1t_results(init_sim_output, optim_output, ...
69 | optim_sim_output, input, sim, vars);
70 |
71 |
--------------------------------------------------------------------------------
/src/matlab_octave/run_2p1t_without_matlab_1.m:
--------------------------------------------------------------------------------
1 | %% Run case study with the two pump single tank network using milp solver outside MATLAB
2 | % -------------------------------------- PART 1 ---------------------------------------
3 | % Requires three steps:
4 | % 1. Convert the model into a mps file and run initial simulation
5 | % 2. Run the mps file using Jupyter notebook in src/python/docs/solve_with_cplex_cbc.ipynb
6 | % and save the optimization output (optimal state vector x) to a mat file
7 | % 3. Read the .mat file and finish the process by running final optimization with the
8 | % optimized schedule and visualise results, i.e. visualise the following:
9 | % (a) Simulation results with initial pump schedule
10 | % (b) Outputs of the linear programme
11 | % (c) Simulation results with optimized pump schedule
12 |
13 | % The process requires running two files:
14 | % 1. run_2p1t_without_matlab_1.m
15 | % 2. run_2p1t_without_matlab_2.m
16 |
17 | final_tank_level = 232.5;
18 |
19 | % In between 1 and 2 it is required to run the optimization outside matlab and transfer the
20 | % generated outputs back to the root directory, i.e. the directory in which this file is located.
21 |
22 | %% Load 2p1t network and input data
23 | [input,const,linprog,network,sim,pump_groups] = initialise_2p1t();
24 |
25 | %% Initialise continuous and binary variable structures required to construct linear programme
26 | vars = initialise_var_structure(network, linprog);
27 |
28 | %% Initial simulation to simulate flows, calculated heads, power consumption and tank levels
29 | init_sim_output = simulator_2p1t(input.init_schedule, network, input, sim);
30 |
31 | %% Transform network to a mixed integer linear model
32 | lin_model = transform_2p1t_to_milp(input, linprog, network, pump_groups, ...
33 | init_sim_output, 1, 1, 1, final_tank_level);
34 |
35 | % Save state for part 2 of the analysis
36 | save("optim_step1.mat", "input", "network", "sim", "vars", "init_sim_output");
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/matlab_octave/run_2p1t_without_matlab_2.m:
--------------------------------------------------------------------------------
1 | %% Run case study with the two pump single tank network using milp solver outside MATLAB
2 | % -------------------------------------- PART 2 ---------------------------------------
3 | % Requires optimization state vector x in file `optim_x.mat` and the outputs of file
4 | % `run_2p1t_without_matlab_1.m` in file `optim_step1.mat`
5 | % Reads the .mat file and finishes the process by running final optimization with the
6 | % optimized schedule and visualise results, i.e. visualise the following:
7 | % (a) Simulation results with initial pump schedule
8 | % (b) Outputs of the linear programme
9 | % (c) Simulation results with optimized pump schedule
10 |
11 | %% Load the required mat files
12 | load("optim_step1.mat");
13 | % Note, if you run cbc change the below path to
14 | % "../python/outputs/2p1t/x_optim_cbc.mat"
15 | load("../python/outputs/2p1t/x_optim_cplex", "x", "obj"); % Needs to load optimization state variable `x`
16 |
17 | disp(obj)
18 |
19 | % Retrieve pump schedule from the optimal state vector x
20 | [n, s] = find_schedule_from_x(1, vars, x);
21 | optim_output.n = n;
22 | optim_output.s = s;
23 | optim_output.x = x;
24 |
25 | %% Obtain schedule structure that is compatible with the simulator
26 | % NOTE: In scheduler, each pump is represented individually whilst simulator models
27 | % groups of pumps as one unit
28 | % NOTE: This only works for a single pump group
29 | [optim_n, optim_s] = linprog_to_sim_schedule(n,s);
30 | optim_output.schedule.N = optim_n;
31 | optim_output.schedule.S = optim_s;
32 |
33 | %% Run the simulator with optimized schedules
34 | optim_sim_output = simulator_2p1t(...
35 | optim_output.schedule, network, input, sim);
36 |
37 | %% Visualise results:
38 | % 1. simulation with initial schedule
39 | % 2. optimizer output
40 | % 3. final_simulation with optimized schedule
41 | folder = "./outputs_16_08";
42 | plot_scheduling_2p1t_results(init_sim_output, optim_output, ...
43 | optim_sim_output, input, sim, vars, folder);
44 |
45 | % Remove intermediate step .mat file `optim_step1.mat`
46 | delete('optim_step1.mat')
47 |
--------------------------------------------------------------------------------
/src/matlab_octave/transform_2p1t_to_milp.m:
--------------------------------------------------------------------------------
1 | function lin_model = transform_2p1t_to_milp(...
2 | input, linprog, network, pump_groups, init_sim_output, ...
3 | save_to_mps, save_to_mat, var_names, final_water_level)
4 | % Process the 2p1t model and create a mixed integer linear programme
5 | % representation (lin_model). Save to mps file and/or mat file
6 | % when required (if save_to_mps and save_to_mat respectively, are set to tru)
7 | sparse_out = 1;
8 | % Find q_op and s_op at 12
9 | pump_speed_12 = input.init_schedule.S(12);
10 | pump_flow_12 = init_sim_output.q(2,12)/input.init_schedule.N(12);
11 | pump_speed_nominal = 1;
12 | pump_flow_max_eff = pump_groups.pump.max_eff_flow;
13 | % Step 4 - Formulate linear program
14 | % Initialise continuous and binary variable structures
15 | vars = initialise_var_structure(network, linprog);
16 | % Set the vector of indices of binary variables in the vector of decision variables x
17 | intcon_vector = set_intcon_vector(vars);
18 | % Set the vector of objective function coefficients c
19 | c_vector = set_objective_vector(vars, network, input, linprog, true);
20 | % Get variable (box) constraints for the two pump one tank network
21 | constraints = set_constraints_2p1t(network);
22 | [lb_vector, ub_vector] = set_variable_bounds(vars, constraints, final_water_level); % Hard coded initial water level
23 | % Linearize the pipe and pump elements
24 | lin_pipes = linearize_pipes_2p1t(network, init_sim_output);
25 | lin_pumps = linearize_pumps_2p1t(pump_groups);
26 | % Linearize the power consumption model
27 | % lin_power = linearize_pump_power_2p1t(pump_groups, pump_flow_12, ...
28 | % pump_speed_12);
29 | % Linearize pump model with dummy constriants and domain vertices
30 | % (as not relevant for linearization)
31 | constraint_signs = {'>', '<', '<', '>'};
32 | domain_vertices = {[0,0], [0,0], [0,0], [0,0]}; % Dummy variable due to the fact that linearize_power_model requires (any) input but in this case the variable does not do anything
33 | lin_power = linearize_power_model(pump_groups(1).pump, pump_flow_12, ...
34 | pump_speed_12, domain_vertices, constraint_signs);
35 | % Set the inequality constraints A and b
36 | [A, b] = set_A_b_matrices(vars, linprog, lin_power, lin_pipes, ...
37 | lin_pumps, pump_groups, sparse_out);
38 | % Set the equality constraints Aeq and beq
39 | [Aeq, beq] = set_Aeq_beq_matrices(vars, network, input, lin_pipes,...
40 | sparse_out);
41 | % Convert all individual matrices and vectors to a linear model structure
42 | lin_model.c_vector = c_vector;
43 | lin_model.A = A;
44 | lin_model.b = b;
45 | lin_model.Aeq = Aeq;
46 | lin_model.beq = beq;
47 | lin_model.lb = lb_vector;
48 | lin_model.ub = ub_vector;
49 | lin_model.intcon = intcon_vector;
50 | % Save to mps standard file, if save_to_mps is True
51 | % TODO: Add 'EleNames', 'EqtNames' for inequality and equality constraint names, respectively
52 | if save_to_mps == true
53 | if var_names == true
54 | % Create a cell with Variable names
55 | for ix=1:length(c_vector)
56 | variable = map_lp_vector_index_to_var(vars, ix);
57 | var_name = variable.subfield_name;
58 | var_subscript = strjoin(string(variable.index), '_');
59 | var_with_subscript = strjoin([var_name, var_subscript], '');
60 | if ix == 1
61 | var_names = {var_with_subscript};
62 | else
63 | var_names(end+1) = {var_with_subscript};
64 | end
65 | end
66 | var_names = cellstr(var_names);
67 | mps_text = BuildMPS(A, b, Aeq, beq, c_vector, lb_vector, ...
68 | ub_vector, 'MILP_Scheduler_2p1t', 'I', intcon_vector,...
69 | 'VarNames', var_names);
70 | else
71 | mps_text = BuildMPS(A, b, Aeq, beq, c_vector, lb_vector, ...
72 | ub_vector, 'MILP_Scheduler_2p1t', 'I', intcon_vector);
73 | end
74 | OK = SaveMPS('../python/data/2p1t/2p1t.mps', mps_text);
75 | end
76 | % Save to a mat file if save_to_map is True
77 | if save_to_mat == true
78 | save('../python/data/2p1t/2p1t_model.mat', '-struct', 'lin_model');
79 | end
80 |
--------------------------------------------------------------------------------
/src/matlab_octave/variable_structures/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomjanus/milp-scheduling/43cc1ce686b8f810ff5aabec2d8035711623e044/src/matlab_octave/variable_structures/README.md
--------------------------------------------------------------------------------
/src/matlab_octave/variable_structures/find_schedule_from_x.m:
--------------------------------------------------------------------------------
1 | function [n, s] = find_schedule_from_x(exitflag, vars, x)
2 | if exitflag >= 1
3 | % Retrieve n and s vectors from x1
4 | n = zeros(size(vars.x_bin.n));
5 | s = zeros(size(vars.x_cont.s));
6 | if size(n) ~= size(s)
7 | error("Arrays of active pump numbers n and pump speeds s are not equal");
8 | end
9 | % Populate n and s arrays with values obtained from intlinprog
10 | for i = 1:size(n,1)
11 | for j = 1:size(n,2)
12 | lin_index_n = lin_index_from_array(vars.x_bin.n, {i,j});
13 | lin_index_s = lin_index_from_array(vars.x_cont.s, {i,j});
14 | lp_index_n = map_var_index_to_lp_vector(vars, 'n', lin_index_n);
15 | lp_index_s = map_var_index_to_lp_vector(vars, 's', lin_index_s);
16 | n(i,j) = x(lp_index_n);
17 | s(i,j) = x(lp_index_s);
18 | end
19 | end
20 | else
21 | n = nan;
22 | s = nan;
23 | end
24 | end
--------------------------------------------------------------------------------
/src/matlab_octave/variable_structures/initialise_var_structure.m:
--------------------------------------------------------------------------------
1 | function vars = initialise_var_structure(network, linprog)
2 | % Create a structure of continuous and binary decision variables and
3 | % initialize the variable vectors with zeros
4 | %
5 | % All variables cover the time horizon n = 1 : no_steps
6 | % The var structure is intended to act as a template for creating the decision
7 | % variable vector, the objective coefficient vector, and the rows of matrices
8 | % A and Aq for the formulation of the mixed integer linear programme.
9 | %
10 | % Args:
11 | % network - network structure
12 | % linprog - linear programme config variables
13 | %
14 | % Returns:
15 | % structure with continuous and binary variable vectors
16 |
17 | no_pipe_segments = linprog.NO_PIPE_SEGMENTS;
18 | no_pump_segments = linprog.NO_PUMP_SEGMENTS;
19 | no_steps = linprog.NO_PRED_STEPS;
20 | number_of_elements = network.npipes + network.npg;
21 |
22 | % TODO: Arguments no_pipe_segments and no_pump_segments should come
23 | % from within linearization functions as they may depend on the type
24 | % and/or parameterization of chosen linearization functions out of
25 | % the set of available linearization options.
26 |
27 | % Initialize continuous variables
28 | x_cont.ht = zeros(no_steps, network.nt);
29 | x_cont.hc = zeros(no_steps, network.nc);
30 | % Introduce a `compound' flow vector that is composed of pipe flows and
31 | % flows via pump groups
32 | x_cont.qel = zeros(no_steps, number_of_elements);
33 | % x_cont.qpipe = zeros(no_steps, network.npipes);
34 | % x_cont.q = zeros(no_steps, network.npumps);
35 | x_cont.ww = zeros(no_pipe_segments, no_steps, network.npipes);
36 | x_cont.ss = zeros(no_pump_segments, no_steps, network.npumps);
37 | x_cont.qq = zeros(no_pump_segments, no_steps, network.npumps);
38 | x_cont.p = zeros(no_steps, network.npumps);
39 | x_cont.q = zeros(no_steps, network.npumps);
40 | x_cont.s = zeros(no_steps, network.npumps);
41 |
42 | % Initialize binary variables
43 | % Binary variables for pipe segment selection
44 | x_bin.bb = zeros(no_pipe_segments, no_steps, network.npipes);
45 | % Binary variables for pump segment selection
46 | x_bin.aa = zeros(no_pump_segments, no_steps, network.npumps);
47 | % Binary variables for pump selection (for every pump in the network separately)
48 | x_bin.n = zeros(no_steps, network.npumps);
49 |
50 | % Create the output var structure
51 | vars.x_cont = x_cont;
52 | vars.x_bin = x_bin;
53 |
54 | % Calculate numbers of continuous and binary variables
55 | vars.n_cont = length(struct_to_vector(vars, 'x_cont'));
56 | vars.n_bin = length(struct_to_vector(vars, 'x_bin'));
57 | end
58 |
--------------------------------------------------------------------------------
/src/matlab_octave/variable_structures/lib/struct_to_vector.m:
--------------------------------------------------------------------------------
1 | function var_vector = struct_to_vector(var_struct, var_type)
2 | % Access all fields in a var struct and output a concatenated variable
3 | % vector
4 | % Works individually for bin and continuous variables
5 | % Args:
6 | % var_struct: var structure containing fields x_cont and x_bin
7 | % var_type (Optional): specifies the type of variable which
8 | % length is to be calculated, i.e. 'x_cont' or 'x_bin'
9 |
10 | if nargin == 2
11 | parsed_fields = {var_type};
12 | else
13 | parsed_fields = {'x_cont', 'x_bin'};
14 | end
15 |
16 | field_names = fieldnames(var_struct);
17 | var_vector = [];
18 |
19 | for k=1:numel(field_names)
20 | field_name = field_names{k};
21 | if ~ismember(field_name, parsed_fields)
22 | continue
23 | end
24 | subfield_names=fieldnames(var_struct.(field_name));
25 | for l=1:numel(subfield_names)
26 | subfield_name = subfield_names{l};
27 | subfield_vector = var_struct.(field_name).(subfield_name);
28 | % Convert the fields to vectors
29 | if number_of_dimensions(subfield_vector) > 1
30 | subfield_vector = tensor_to_vector(subfield_vector);
31 | end
32 | var_vector = [var_vector; subfield_vector];
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/src/matlab_octave/variable_structures/lib/vector_to_struct.m:
--------------------------------------------------------------------------------
1 | function out = vector_to_struct(var_vector, var_struct)
2 | % Takes the variable vector (Nx1) and remaps it back to a
3 | % variable struct of a structure that was used in the first
4 | % place to produce the vector itself.
5 | % Args:
6 | % var_vector - vector of variables constructed from a variable
7 | % struct of the same `structure` to var_struct using
8 | % `vector_to_struct` function.
9 | % var_struct - variable structure used as a template and
10 | % variable container (output).
11 |
12 | end
13 |
--------------------------------------------------------------------------------
/src/matlab_octave/variable_structures/map_lp_vector_index_to_var.m:
--------------------------------------------------------------------------------
1 | function y = map_lp_vector_index_to_var(var_structure, var_index)
2 | % Takes an index from a vector of variables in linprog formulation and
3 | % returns the field and subfield name and index of this variable in
4 | % var_struct
5 |
6 | % Assumption:
7 | % var_structure needs to contain two fields: x_cont and x_bin
8 |
9 | % Args:
10 | % var_structure - variable structue with continous and binary variables
11 | % var_index - index within the linprog variable vector
12 | parsed_fields = {'x_cont', 'x_bin'};
13 |
14 | % Initialize the output structure
15 | y.field_name = '';
16 | y.subfield_name = '';
17 | y.index = nan;
18 |
19 | start_index = 0;
20 | for i=1:numel(parsed_fields)
21 | % Only select the variable fields: x_cont and x_bin
22 | field_name = parsed_fields{i};
23 | % If we are dealing with the vectors
24 | % Iterate through the variables - continuous first and then binary
25 | var_names = fieldnames(var_structure.(field_name));
26 | for j=1:numel(var_names)
27 | var_j_name = var_names{j};
28 | subfield_array = var_structure.(field_name).(var_j_name);
29 | var_j_length = numel(subfield_array);
30 | lb_index = start_index;
31 | end_index = start_index + var_j_length;
32 | if var_index > lb_index && var_index <= end_index
33 | % (Sub)field found
34 | y.field_name = field_name;
35 | y.subfield_name = var_j_name;
36 | n_array_dim = number_of_dimensions(subfield_array);
37 | % Work on arrays of different dimensions
38 | if n_array_dim == 1
39 | y.index(1) = sub_from_array(...
40 | subfield_array,(var_index - lb_index));
41 | elseif n_array_dim == 2
42 | [y.index(1), y.index(2)] = sub_from_array(...
43 | subfield_array,(var_index - lb_index));
44 | elseif n_array_dim == 3
45 | [y.index(1), y.index(2), y.index(3)] = sub_from_array(...
46 | subfield_array,(var_index - lb_index));
47 | else
48 | error('Only arrays of dimension 1, 2 and 3 are supported.');
49 | end
50 | return
51 | end
52 | start_index = end_index;
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/src/matlab_octave/variable_structures/map_var_index_to_lp_vector.m:
--------------------------------------------------------------------------------
1 | function y = map_var_index_to_lp_vector(var_structure, var_name, var_index)
2 | % Takes an index from a vector of variables of a given type and returns the
3 | % index to this variable in the composite vector of variables x used in the
4 | % MILP formulation
5 |
6 | % Assumption:
7 | % var_structure needs to contain two fields: x_cont and x_bin
8 |
9 | % Args:
10 | % var_structure - variable structue with continous and binary variables
11 | % var_name - variable name
12 | % var_index - index within the variable vector (integer) or a cell of
13 | % subscripts, e.g. {2,4}
14 |
15 | isaninteger = @(x)isfinite(x) & x==floor(x);
16 |
17 | % Returns index pointing to the variable in the vector x of MILP formulation
18 | fn=fieldnames(var_structure);
19 | % Loop through the fields
20 | parsed_fields = {'x_cont', 'x_bin'};
21 |
22 | field_found = false;
23 | start_index = 0;
24 |
25 | for i=1:numel(fn)
26 | % Only select the variable fields: x_cont and x_bin
27 | field_name = fn{i};
28 | % If we are dealing with the vectors
29 | if ismember(field_name, parsed_fields)
30 | % Iterate through the variables - continuous first and then binary
31 | var_names = fieldnames(var_structure.(field_name));
32 | for j=1:numel(var_names)
33 | var_j_name = var_names{j};
34 | var_j_length = numel(var_structure.(field_name).(var_j_name));
35 | if strcmp(var_j_name, var_name) == true
36 | field_found = true;
37 | break
38 | end
39 | start_index = start_index + var_j_length;
40 | end
41 | if field_found == true
42 | break
43 | end
44 | end
45 | end
46 | % Throw error if the requested field could not be found
47 | if field_found ~= true
48 | error('Variable %s not found', var_name);
49 | else
50 | if iscell(var_index)
51 | var_index_str = mat2str(cell2mat(var_index));
52 | var_index = lin_index_from_array(...
53 | var_structure.(field_name).(var_j_name), var_index);
54 | end
55 | end
56 | if isaninteger(var_index)
57 | var_index_str = int2str(var_index);
58 | end
59 | % Throw error if the requested index is beyond the vector's length
60 | if var_index > var_j_length
61 | error('Index %s of field %s beyond range', var_index_str, var_name);
62 | end
63 |
64 | y = start_index + var_index;
65 | end
66 |
--------------------------------------------------------------------------------
/src/python/data/2p1t/README.md:
--------------------------------------------------------------------------------
1 | Folder for storing representations of linear programmes
2 | Files stored *.lp, *.mps, *.mat
3 |
--------------------------------------------------------------------------------
/src/python/docs/run_cplex.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 | import argparse
3 | import os
4 | import cplex
5 | import mip
6 | import scipy.io
7 |
8 | #MAT_FILE = pathlib.Path("../data/2p1t/2p1t_model.mat")
9 | MPS_FILE = pathlib.Path("../data/2p1t/2p1t.mps")
10 | CPLEX_RESULTS_FILE = pathlib.Path("../outputs/2p1t/x_optim_cplex.mat")
11 | # Parrarel (multihreaded) execution
12 | os_threads = os.cpu_count()
13 |
14 | def run_cplex(
15 | mps_file: pathlib.Path, cplex_result: pathlib.Path,
16 | mip_gap: float = 0.001, num_threads: int | None = os_threads,
17 | mps_lp_convert: bool = True, debug: bool = True) -> None:
18 | """Reads the milp model saved in the mps_file and saves results to
19 | a mat file given in cplex_resuts."""
20 |
21 | # Convert MPS file to LP file
22 | if mps_lp_convert:
23 | # convert mps to lp using the mip package
24 | instance = mip.Model()
25 | instance.read(mps_file.as_posix())
26 | lp_file = mps_file.with_suffix(".lp")
27 | instance.write(lp_file.as_posix())
28 |
29 | problem = cplex.Cplex()
30 | problem.parameters.threads.set(num_threads)
31 | # problem.parameters.benders.strategy = -1
32 | problem.read(mps_file.as_posix())
33 | problem.set_problem_type(cplex.Cplex.problem_type.MILP)
34 | problem.parameters.mip.tolerances.mipgap.set(mip_gap)
35 | problem.solve()
36 | x_optim = problem.solution.get_values()
37 | if debug:
38 | problem.report()
39 | problem.print_information()
40 | print(f"Solution status: {problem.solution.get_status_string()}")
41 | print(f"Objective value: {problem.solution.get_objective_value()}")
42 | scipy.io.savemat(cplex_result, {"x": x_optim})
43 |
44 | def cli() -> None:
45 | """ """
46 | parser = argparse.ArgumentParser(description="Run CPLEX with given arguments")
47 | parser.add_argument("mps_file", type=pathlib.Path, help="Path to the MPS file")
48 | parser.add_argument("cplex_result", type=pathlib.Path, help="Path to the CPLEX result file")
49 | parser.add_argument("--mip_gap", type=float, default=0.001,
50 | help="MIP gap tolerance")
51 | parser.add_argument("--num_threads", type=int, default=os.cpu_count(),
52 | help="Number of threads to use")
53 | parser.add_argument("--no_mps_lp_convert", dest="mps_lp_convert",
54 | action="store_false", help="Disable MPS to LP conversion")
55 | parser.add_argument("--no_debug", dest="debug", action="store_false",
56 | help="Disable debugging mode")
57 | args = parser.parse_args()
58 |
59 | run_cplex(
60 | args.mps_file,
61 | args.cplex_result,
62 | mip_gap=args.mip_gap,
63 | num_threads=args.num_threads,
64 | mps_lp_convert=args.mps_lp_convert,
65 | debug=args.debug)
66 |
67 |
68 | if __name__ == "__main__":
69 | """ """
70 | cli()
71 |
--------------------------------------------------------------------------------
/src/python/requirements.txt:
--------------------------------------------------------------------------------
1 | fpdf==1.7.2
2 | cplex
3 | mip
4 | scipy
5 |
--------------------------------------------------------------------------------
/src/python/src/utils/__init__.py:
--------------------------------------------------------------------------------
1 | """ """
2 | import importlib.metadata
3 | __version__ = importlib.metadata.version('python_tools')
4 |
--------------------------------------------------------------------------------
/src/python/src/utils/generate_pdf_debug_report.py:
--------------------------------------------------------------------------------
1 | """ Create a PDF report with specification of the MILP problem formulation from
2 | a collection of .report text files."""
3 | from typing import List
4 | import argparse
5 | import logging
6 | import io
7 | import os
8 | from fpdf import FPDF
9 |
10 |
11 | logging.basicConfig(level=logging.INFO)
12 |
13 |
14 | def find_files(dir_path: str, ext: str) -> List[str]:
15 | """ """
16 | # Make sure extension is in a (dot)ext format, e.g. .docx
17 | if not ext.startswith("."):
18 | ext = "".join([".", ext])
19 | return [
20 | os.path.join(dir_path, file) for file in os.listdir(dir_path)
21 | if file.endswith(ext)]
22 |
23 |
24 | def write_lines_to_pdf_cell(
25 | file_handle: io.TextIOWrapper, pdf_object: FPDF,
26 | line_width: int = 5) -> None:
27 | """ """
28 | for line in file_handle:
29 | pdf_object.cell(0, line_width, ln=1, txt=line)
30 |
31 |
32 | def convert_reports_to_pdf(
33 | reports_folder: str, output_file: str, verbose: bool) -> None:
34 | """ """
35 | pdf = FPDF()
36 | pdf.set_font("Arial", size=10)
37 | pdf.set_title("MILP formulation report")
38 | subfolders = (f.path for f in os.scandir(reports_folder) if f.is_dir())
39 | for subfolder in subfolders:
40 | if verbose:
41 | logging.info("Writing reports in folder %s", subfolder)
42 | report_files = find_files(subfolder, ext=".report")
43 | for file in report_files:
44 | pdf.add_page()
45 | with open(file, "r") as file_handle:
46 | write_lines_to_pdf_cell(file_handle, pdf)
47 | pdf.output(output_file)
48 | if verbose:
49 | logging.info("Reports saved to file %s", output_file)
50 |
51 |
52 | if __name__ == "__main__":
53 | """Run the function in the CLI mode"""
54 | parser = argparse.ArgumentParser()
55 | parser.add_argument("reports_folder", help="Path of the reports directories")
56 | parser.add_argument("output_file", help="Path to the output PDF file")
57 | parser.add_argument("--verbose", help="Additional output information",
58 | action="store_true")
59 | args = parser.parse_args()
60 | convert_reports_to_pdf(args.reports_folder, args.output_file, args.verbose)
61 |
--------------------------------------------------------------------------------
/tests/matlab_octave/README.md:
--------------------------------------------------------------------------------
1 | # Tests
2 |
--------------------------------------------------------------------------------
/tests/matlab_octave/debug_2p1t/check_c_vector.m:
--------------------------------------------------------------------------------
1 | function y = check_c_vector(c_vector, vars)
2 | % Check the nonzero indices in the objective vector and map them to the variable structure
3 | y = 0;
4 | fprintf("Checking the c vector ...\n");
5 | fprintf("Generating report...\n");
6 | report_title = "Objective vector c";
7 | filename = "reports/objective_function/c.report";
8 | vector_report(report_title, filename, c_vector, vars);
9 | fprintf("Done.\n");
10 | fprintf("Validating...\n");
11 | nonzero_indices = find(c_vector);
12 | p_vec_indices = get_array_indices(vars.x_cont.p);
13 | number_nozero_indices = length(nonzero_indices);
14 | var_names = cell(number_nozero_indices, 1);
15 | indices = zeros(size(p_vec_indices));
16 | for i = 1:number_nozero_indices
17 | ix = nonzero_indices(i);
18 | out = map_lp_vector_index_to_var(vars, ix);
19 | var_names{i} = out.subfield_name;
20 | indices(i, :) = out.index;
21 | end
22 | % Check if the variables are corresponding to power consumption
23 | if ~any(strcmp(var_names,'p'))
24 | error('some of the variables do not point to power (p)');
25 | end
26 | % Check if all of the indices match
27 | assert(isequal(indices,p_vec_indices), "Some of the indices do not match");
28 | % There should be 48 nonzero coefficients: 24 hours x 2 pumps
29 | assert(isequal(number_nozero_indices, 48), "Number of variables should equal 48");
30 | fprintf("c vector OKAY. \n\n");
31 | y = 1;
32 | end
33 |
--------------------------------------------------------------------------------
/tests/matlab_octave/debug_2p1t/check_equality_constraints.m:
--------------------------------------------------------------------------------
1 | function y = check_equality_constraints(network, vars, lin_pipes, sim_input)
2 | % Calculate network equality constraints
3 | [Aeq_ht, beq_ht] = ht_constraints(vars, network);
4 | [Aeq_nodeq, beq_nodeq] = nodeq_constraints(vars, network, sim_input);
5 | [Aeq_pumpgroup, beq_pumpgroup] = pumpgroup_constraints(vars, network);
6 | [Aeq_dhpipe, beq_dhpipe] = pipe_headloss_constraints(...
7 | vars, network, sim_input, lin_pipes);
8 | % Calculate equality constraints related to linearization
9 | [Aeq_qpipe, beq_qpipe] = ww_constraints(vars, network);
10 | [Aeq_bb, beq_bb] = bb_constraints(vars);
11 | [Aeq_ss, beq_ss] = ss_constraints(vars);
12 | [Aeq_qq, beq_qq] = qq_constraints(vars);
13 | [Aeq_aa, beq_aa] = aa_constraints(vars);
14 | % Create reports
15 | eq_ineq_constraint_report(...
16 | "Tank head equality constraints", ...
17 | "reports/equality/ht_constraints.report", ...
18 | Aeq_ht, beq_ht, vars);
19 | eq_ineq_constraint_report(...
20 | "Flow continuity constraints", ...
21 | "reports/equality/nodeq_constraints.report", ...
22 | Aeq_nodeq, beq_nodeq, vars);
23 | eq_ineq_constraint_report(...
24 | "Pump group flow equality constraints",...
25 | "reports/equality/pumpgroup_constraints.report",...
26 | Aeq_pumpgroup, beq_pumpgroup, vars);
27 | eq_ineq_constraint_report(...
28 | "Pipe headloss equality constraints",...
29 | "reports/equality/dhpipe_constraints.report",...
30 | Aeq_dhpipe, beq_dhpipe, vars);
31 | eq_ineq_constraint_report(...
32 | "Pipe segment flow equality constraints",...
33 | "reports/equality/qpipe_constraints.report",...
34 | Aeq_qpipe, beq_qpipe, vars);
35 | eq_ineq_constraint_report(...
36 | "Pipe segment selection equality constraints",...
37 | "reports/equality/bb_constraints.report",...
38 | Aeq_bb, beq_bb, vars);
39 | eq_ineq_constraint_report(...
40 | "Pump segment speed equality constraints",...
41 | "reports/equality/ss_constraints.report",...
42 | Aeq_ss, beq_ss, vars);
43 | eq_ineq_constraint_report(...
44 | "Pump segment flow equality constraints",...
45 | "reports/equality/qq_constraints.report",...
46 | Aeq_qq, beq_qq, vars);
47 | eq_ineq_constraint_report(...
48 | "Pump segment selection equality constraints",...
49 | "reports/equality/aa_constraints.report",...
50 | Aeq_aa, beq_aa, vars);
51 | end
52 |
--------------------------------------------------------------------------------
/tests/matlab_octave/debug_2p1t/check_inequality_constraints.m:
--------------------------------------------------------------------------------
1 | function y = check_inequality_constraints(vars, linprog, lin_power, lin_pipes, lin_pumps, pump_groups)
2 | % Calculate network inequality constraints
3 | [A_p, b_p] = power_ineq_constraint(vars, linprog);
4 | [A_p_lin, b_p_lin] = power_model_ineq_constraint(vars, lin_power, linprog);
5 | [A_pipe, b_pipe] = pipe_flow_segment_constraints(vars, lin_pipes, linprog);
6 | [A_s_box, b_s_box] = s_box_constraints(vars, pump_groups, linprog);
7 | [A_q_box, b_q_box] = q_box_constraints(vars, pump_groups, linprog);
8 | [A_pumpeq, b_pumpeq] = pump_equation_constraints(vars, linprog, lin_pumps);
9 | [A_pump_domain, b_pump_domain] = pump_domain_constraints(vars, linprog, ...
10 | lin_pumps);
11 |
12 | % Create reports
13 | eq_ineq_constraint_report(...
14 | "Power inequality constraints", ...
15 | "reports/inequality/power_ineq_constraint.report", ...
16 | A_p, b_p, vars);
17 | eq_ineq_constraint_report(...
18 | "Linear power model inequality constraints", ...
19 | "reports/inequality/power_model_ineq_constraint.report", ...
20 | A_p_lin, b_p_lin, vars);
21 | eq_ineq_constraint_report(...
22 | "Pipe flow segment inequality constraints", ...
23 | "reports/inequality/pipe_flow_segment_ineq_constraint.report", ...
24 | A_pipe, b_pipe, vars);
25 | eq_ineq_constraint_report(...
26 | "Linearized pump speed inequality constraints", ...
27 | "reports/inequality/s_box_ineq_constraint.report", ...
28 | A_s_box, b_s_box, vars);
29 | eq_ineq_constraint_report(...
30 | "Linearized pump flow inequality constraints", ...
31 | "reports/inequality/q_box_ineq_constraint.report", ...
32 | A_q_box, b_q_box, vars);
33 | eq_ineq_constraint_report(...
34 | "Pump equation inequality constraints", ...
35 | "reports/inequality/pump_equation_ineq_constraint.report", ...
36 | A_pumpeq, b_pumpeq, vars);
37 | eq_ineq_constraint_report(...
38 | "Pump domain inequality constraints", ...
39 | "reports/inequality/pump_domain_ineq_constraint.report", ...
40 | A_pump_domain, b_pump_domain, vars);
41 | end
42 |
--------------------------------------------------------------------------------
/tests/matlab_octave/debug_2p1t/check_intcon_vector.m:
--------------------------------------------------------------------------------
1 | function y = check_intcon_vector(intcon_vector, vars)
2 | % Check which variables are marked as integer variables
3 | % in the intcon variable vector and generate report
4 | y = 0;
5 | fprintf("Checking the intcon vector ...\n");
6 | number_intcon_indices = length(intcon_vector);
7 | fprintf("Number of integer variables %d \n", number_intcon_indices);
8 | report_title = "Integer variables";
9 | filename = "reports/intcon_vector/intcon.report";
10 | intcon_vector_report(report_title, filename, intcon_vector, vars);
11 | fprintf("Checking the intcon vector finished.\n");
12 | y = 1;
13 | end
14 |
--------------------------------------------------------------------------------
/tests/matlab_octave/debug_2p1t/check_lb_vector.m:
--------------------------------------------------------------------------------
1 | function y = check_lb_vector(lb_vector, vars)
2 | % Check the nonzero indices in the objective vector and map them to the variable structure
3 | y = 0;
4 | fprintf("Checking the lb vector ...\n");
5 | fprintf("Generating report...\n");
6 | report_title = "Vector of lower bounds";
7 | filename = "reports/upper_lower_bounds/lb.report";
8 | vector_report(report_title, filename, lb_vector, vars);
9 | fprintf("Done.\n");
10 | y = 1;
11 | end
12 |
--------------------------------------------------------------------------------
/tests/matlab_octave/debug_2p1t/check_ub_vector.m:
--------------------------------------------------------------------------------
1 | function y = check_ub_vector(ub_vector, vars)
2 | % Check the nonzero indices in the objective vector and map them to the variable structure
3 | y = 0;
4 | fprintf("Checking the ub vector ...\n");
5 | fprintf("Generating report...\n");
6 | report_title = "Vector of upper bounds";
7 | filename = "reports/upper_lower_bounds/ub.report";
8 | vector_report(report_title, filename, ub_vector, vars);
9 | fprintf("Done.\n");
10 | y = 1;
11 | end
12 |
--------------------------------------------------------------------------------
/tests/matlab_octave/debug_2p1t/eq_ineq_constraint_report.m:
--------------------------------------------------------------------------------
1 | function y = eq_ineq_constraint_report(report_title, filename, A_matrix, b_vector, vars)
2 | % Writes a report about constraint formulation into a text file
3 | y = 0;
4 | fileID = fopen(filename,'w');
5 | fprintf(fileID, "REPORT TITLE: %s \n", report_title);
6 | fprintf(fileID, "------------------------ REPORT START -----------------------------\n");
7 | for i=1:length(b_vector)
8 | fprintf(fileID, "Entry %d \n", i);
9 | fprintf(fileID, "\tRHS = %.1f \n", b_vector(i));
10 | nonzero_indices = find(A_matrix(i,:));
11 | fprintf(fileID, "\tLHS: \n");
12 | for j = 1:length(nonzero_indices)
13 | ix = nonzero_indices(j);
14 | out_map = map_lp_vector_index_to_var(vars, ix);
15 | coeff = A_matrix(i,ix);
16 | variable = out_map.subfield_name;
17 | var_index = out_map.index;
18 | var_index_str = strjoin(string(var_index), ', ');
19 | fprintf(fileID, "\t\tCoefficient: %f , Variable: %s , Index: [%s] \n", ...
20 | coeff, variable, var_index_str);
21 | end
22 | end
23 | fprintf(fileID, "------------------------ REPORT END -----------------------------\n");
24 | fclose(fileID);
25 | y = 1;
26 | end
27 |
--------------------------------------------------------------------------------
/tests/matlab_octave/debug_2p1t/intcon_vector_report.m:
--------------------------------------------------------------------------------
1 | function y = intcon_vector_report(report_title, filename, vector, vars)
2 | % Writes a report about coefficients stored in a vector
3 | % The positions of coefficients in the vector are mapped
4 | % onto the vars structure.
5 | y = 0;
6 | fileID = fopen(filename,'w');
7 | vector_length = length(vector);
8 | fprintf(fileID, "REPORT TITLE: %s \n", report_title);
9 | fprintf(fileID, "------------------------ REPORT START -----------------------------\n");
10 | fprintf(fileID, "Vector length: %d\n", vector_length);
11 | for ix=1:length(vector)
12 | out_map = map_lp_vector_index_to_var(vars, vector(ix));
13 | variable = out_map.subfield_name;
14 | var_index = out_map.index;
15 | var_index_str = strjoin(string(var_index), ', ');
16 | fprintf(fileID, "Entry %d \t Value %d \t Variable %s \t Index: [%s] \n", ix, vector(ix), variable, var_index_str);
17 | end
18 | fprintf(fileID, "------------------------ REPORT END -----------------------------\n");
19 | fclose(fileID);
20 | y = 1;
21 | end
22 |
--------------------------------------------------------------------------------
/tests/matlab_octave/debug_2p1t/reports/README.md:
--------------------------------------------------------------------------------
1 | # Folder for storing debugging reports in text .report format
2 |
3 | The reports are stored in four subfolders:
4 | - `equality/`
5 | - `inequality/`
6 | - `intcon_vector`
7 | - `objective_function/`
8 | - `upper_lower_bounds/`
--------------------------------------------------------------------------------
/tests/matlab_octave/debug_2p1t/run_debug.m:
--------------------------------------------------------------------------------
1 | % Run functions testing the correctness of MILP formulation for the 2p1t system
2 | sparse_out = 1;
3 |
4 | % Initialize variable structures required to run the MILP pump scheduling problem
5 | [sim_input,const,linprog,network,sim,pump_groups] = initialise_2p1t();
6 | output = simulator_2p1t(sim_input.init_schedule, network, sim_input, sim);
7 | % Find q_op and s_op at 12
8 | pump_speed_12 = sim_input.init_schedule.S(12);
9 | pump_flow_12 = output.q(2,12)/sim_input.init_schedule.N(12);
10 |
11 | vars = initialise_var_structure(network, linprog);
12 | intcon_vector = set_intcon_vector(vars);
13 | c_vector = set_objective_vector(vars, network, sim_input, linprog, true);
14 | % Get variable (box) constraints for the two pump one tank network
15 | constraints = set_constraints_2p1t(network);
16 | [lb_vector, ub_vector] = set_variable_bounds(vars, constraints);
17 | % Linearize the pipe and pump elements
18 | lin_pipes = linearize_pipes_2p1t(network, output);
19 | lin_pumps = linearize_pumps_2p1t(pump_groups);
20 | % Linearize the power consumption model
21 | % lin_power = linearize_pump_power_2p1t(pump_groups, pump_flow_12, ...
22 | % pump_speed_12);
23 | % Linearize pump model with dummy constraints and domain vertices
24 | % (as not relevant for linearization)
25 | constraint_signs = {'>', '<', '<', '>'};
26 | domain_vertices = {[0,0], [0,0], [0,0], [0,0]};
27 | lin_power = linearize_power_model(pump_groups(1).pump, pump_flow_12, pump_speed_12, ...
28 | domain_vertices, constraint_signs);
29 | % Set the inequality constraints A and b
30 | [A, b] = set_A_b_matrices(vars, linprog, lin_power, lin_pipes, ...
31 | lin_pumps, pump_groups, sparse_out);
32 | % Set the equality constraints Aeq and beq
33 | [Aeq, beq] = set_Aeq_beq_matrices(vars, network, sim_input, lin_pipes,...
34 | sparse_out);
35 |
36 | % Run checks and generate reports for manual inspection and debugging
37 | check_inequality_constraints(vars, linprog, lin_power, lin_pipes, lin_pumps, pump_groups);
38 | check_equality_constraints(network, vars, lin_pipes, sim_input);
39 | check_intcon_vector(intcon_vector, vars);
40 | check_c_vector(c_vector, vars);
41 | check_lb_vector(lb_vector, vars);
42 | check_ub_vector(ub_vector, vars);
43 |
--------------------------------------------------------------------------------
/tests/matlab_octave/debug_2p1t/vector_report.m:
--------------------------------------------------------------------------------
1 | function y = vector_report(report_title, filename, vector, vars)
2 | % Writes a report about coefficients stored in a vector
3 | % The positions of coefficients in the vector are mapped
4 | % onto the vars structure.
5 | y = 0;
6 | fileID = fopen(filename,'w');
7 | nonzero_indices = find(vector);
8 | fprintf(fileID, "REPORT TITLE: %s \n", report_title);
9 | fprintf(fileID, "------------------------ REPORT START -----------------------------\n");
10 | fprintf(fileID, "Vector length: %d\n", length(vector));
11 | fprintf(fileID, "Number of non-zero elements: %d\n", length(nonzero_indices));
12 | if issparse(vector)
13 | % NOTE: fprintf does not work with sparse matrix representations
14 | vector = full(vector);
15 | end
16 | for ix=1:length(nonzero_indices)
17 | out_map = map_lp_vector_index_to_var(vars, nonzero_indices(ix));
18 | variable = out_map.subfield_name;
19 | var_index = out_map.index;
20 | var_index_str = strjoin(string(var_index), ', ');
21 | fprintf(fileID, "Non-zero entry %d \t Index %d \t Value %f \t Variable %s \t Index: [%s] \n", ...
22 | ix, nonzero_indices(ix), vector(nonzero_indices(ix)), variable, var_index_str);
23 | end
24 | fprintf(fileID, "------------------------ REPORT END -----------------------------\n");
25 | fclose(fileID);
26 | y = 1;
27 | end
28 |
--------------------------------------------------------------------------------
/tests/matlab_octave/run_tests.m:
--------------------------------------------------------------------------------
1 | tests = {'test_intcon_vector_creation.m', 'test_power_linearization.m', ...
2 | 'test_A_b_creation.m', 'test_obj_vector_creation.m', ...
3 | 'test_pump_linearization.m', 'test_Aeq_beq_creation.m', ...
4 | 'test_pipe_linearization.m', 'test_var_intlinprog_vec_mapping.m'};
5 | for test_index = 1:numel(tests)
6 | run(tests{test_index})
7 | end
8 |
--------------------------------------------------------------------------------
/tests/matlab_octave/test_A_b_creation.m:
--------------------------------------------------------------------------------
1 | % EMPTY
2 |
--------------------------------------------------------------------------------
/tests/matlab_octave/test_Aeq_beq_creation.m:
--------------------------------------------------------------------------------
1 | % EMPTY
2 |
--------------------------------------------------------------------------------
/tests/matlab_octave/test_intcon_vector_creation.m:
--------------------------------------------------------------------------------
1 | function y = test_intcon_vector_creation()
2 | % Tests the procedures for obtaining the intcon vector for the MILP
3 | % programme formulation in MATLAB's `intlinprog`
4 | % Returns 1 if the length of the intcon vector == no. of binary variables
5 | % Otherwise, returns 0.
6 | warning('off','all');
7 | test_name = 'test_intcon_vector_creation';
8 | fprintf('\nRunning test: %s \n', test_name);
9 | % Prepare a set of test parameters
10 | test_network.nt = 1;
11 | test_network.nc = 4;
12 | test_network.npipes = 4;
13 | % TODO: should this be npumps or number of pump groups?
14 | test_network.npg = 2;
15 | test_network.npumps = 2;
16 |
17 | % TODO: TEST BOTH THE VECTOR AND TENSOR VAR STRUCTURES
18 | linprog.NO_PIPE_SEGMENTS = 3;
19 | linprog.NO_PUMP_SEGMENTS = 4;
20 | linprog.NO_PRED_STEPS = 24;
21 | linprog.TIME_STEP = 1;
22 |
23 | vars = initialise_var_structure(test_network, linprog);
24 | no_vars_in_struct = var_struct_length(vars);
25 | fprintf('Number of variables in the variable struct = %d \n',...
26 | no_vars_in_struct);
27 | no_bin_vars = var_struct_length(vars, 'x_bin');
28 | fprintf('Number of int variables in the variable struct = %d \n',...
29 | no_bin_vars);
30 | intcon = set_intcon_vector(vars);
31 | fprintf('Created intcon vector of length %d\n', length(intcon));
32 | if no_bin_vars == length(intcon)
33 | y = 1;
34 | else
35 | y = 0;
36 | end
37 | fprintf('Test: %s complete. \n\n', test_name);
38 | warning('on','all');
39 | end
--------------------------------------------------------------------------------
/tests/matlab_octave/test_obj_vector_creation.m:
--------------------------------------------------------------------------------
1 | function y = test_obj_vector_creation()
2 | %
3 | warning('off','all');
4 | test_name = 'test_obj_vector_creation';
5 | fprintf('\nRunning test: %s \n', test_name);
6 | % Set test input values
7 | test_time_step = 1;
8 | sparse_out = true;
9 | test_no_steps = 24;
10 | test_tarif_vec = ones(test_no_steps,1);
11 | % Creae a test network structure
12 | test_network.nt = 1;
13 | test_network.nc = 4;
14 | test_network.npipes = 4;
15 | % TODO: should this be npumps or number of pump groups?
16 | test_network.npg = 2;
17 | test_network.npumps = 2;
18 |
19 | linprog.NO_PIPE_SEGMENTS = 3;
20 | linprog.NO_PUMP_SEGMENTS = 4;
21 | linprog.NO_PRED_STEPS = 24;
22 | linprog.TIME_STEP = 1;
23 |
24 | % Create a test vars structure
25 | test_vars = initialise_var_structure(test_network, linprog);
26 |
27 | test_input.tariff = test_tarif_vec;
28 |
29 | test_f_vector = set_objective_vector(test_vars, test_network, test_input, linprog, 1);
30 |
31 | % Check how many values in the test vector are greated than one and if the
32 | % number is equal to test_no_steps * test_network.npumps
33 |
34 | number_nonzero_elements = length(find(test_f_vector>0));
35 | if number_nonzero_elements == test_no_steps * test_network.npumps
36 | y = 1;
37 | else
38 | y = 0;
39 | end
40 | warning('on','all');
41 | fprintf('Test: %s complete. \n\n', test_name);
42 | end
43 |
--------------------------------------------------------------------------------
/tests/matlab_octave/test_pipe_linearization.m:
--------------------------------------------------------------------------------
1 | function y = test_pipe_linearization()
2 | warning('off','all');
3 | test_name = 'test_pipe_linearization';
4 | fprintf('\nRunning test: %s \n', test_name);
5 | % Provide test pipe and linearization data
6 | R_test = 0.00035391;
7 | q_op_test = 50;
8 | Upipe_test = 150;
9 | % Linearize the pipe characteristic
10 | lin_model = linearize_pipe_characteristic(R_test, q_op_test, Upipe_test);
11 | % Compare head-drops between the nonlinear and linear model for a range of
12 | % points
13 | % Pick operating points
14 | test_flows = [0, 5, 15, 30, 40, 50, 80, 100, 150, 200];
15 | % TODO:
16 | % Segments are currently hard coded but could be made dependent on q_op_test
17 | segments = [2, 2, 2, 2, 2, 2, 3, 3, 3, 3];
18 | if (length(test_flows) ~= length(segments))
19 | error('Points and segments vectors do not have equal lengths');
20 | end
21 | fprintf('----------------------------------------------------------------\n');
22 | fprintf('Comparison of nonlinear and linearized pipe models\n');
23 | for i = 1:length(test_flows)
24 | % Find head-drop from nonlinear characteristic
25 | dh_nl = pipe_characteristic(R_test, test_flows(i));
26 | % Find head-drop from linearized characteristic
27 | dh_lin = lin_model(segments(i)).coeffs.m * test_flows(i) + ...
28 | lin_model(segments(i)).coeffs.c;
29 | fprintf('Point %d, Flow %.1f L/s , Headloss %.2f m, perc. error %.1f , abs. error %.3f m\n', ...
30 | i, test_flows(i), dh_nl, perc_error(dh_nl, dh_lin), abs_error(dh_nl, dh_lin));
31 | end
32 | fprintf('----------------------------------------------------------------\n');
33 | warning('on','all');
34 | fprintf('Test: %s complete. \n\n', test_name);
35 | y = 1;
36 | end
37 |
38 |
39 |
--------------------------------------------------------------------------------
/tests/matlab_octave/test_power_linearization.m:
--------------------------------------------------------------------------------
1 | function y = test_power_linearization()
2 | warning('off','all');
3 | test_name = 'test_power_linearization';
4 | fprintf('\nRunning test: %s \n', test_name);
5 | % Create a test pump object
6 | test_pump.ep = 0.0;
7 | test_pump.fp = 0.0;
8 | test_pump.gp = 0.2422;
9 | test_pump.hp = 40;
10 | test_pump.max_eff_flow = 20;
11 | test_pump.smin = 0.7;
12 | test_pump.smax = 1.2;
13 | test_pump.A = -0.0045000;
14 | test_pump.B = 0;
15 | test_pump.C = 45;
16 |
17 | % Find the operating point
18 | s_op = 1;
19 | q_op = test_pump.max_eff_flow;
20 |
21 | % Specify domain vertices
22 | q_min = 0;
23 | s_min = test_pump.smin;
24 | s_max = test_pump.smax;
25 |
26 | q_int_smin = pump_intercept_flow(test_pump, 1, s_min);
27 | q_int_smax = pump_intercept_flow(test_pump, 1, s_max);
28 | % Use coordinates:
29 | % s
30 | % |
31 | % | *p2 *p3
32 | % |
33 | % | *p1 *p4
34 | % |______________q
35 | %
36 | p1 = [q_min, s_min, pump_head(test_pump, q_min, 1, s_min)]; % min-min
37 | p2 = [q_min, s_max, pump_head(test_pump, q_min, 1, s_max)]; % min-max
38 | p3 = [q_int_smax , s_max, pump_head(test_pump, q_int_smax, 1, s_max)]; % max-max
39 | p4 = [q_int_smin , s_min, pump_head(test_pump, q_int_smin, 1, s_min)]; % max-min
40 |
41 | % Specify a cell with constraint signs
42 | constraint_signs = {'>', '<', '<', '>'};
43 | domain_vertices = {p1, p2, p3, p4};
44 |
45 | % Linearize the power consumption model
46 | out = linearize_power_model(test_pump, q_op, s_op, ...
47 | domain_vertices, constraint_signs);
48 |
49 | m_dq = out(1).coeff(1);
50 | m_ds = out(1).coeff(2);
51 | c = out(1).coeff(3);
52 | % Get a number of flow and s pairs
53 | q_s_array = [0, 0.7;
54 | 15, 0.7;
55 | 35, 1.2;
56 | 20, 1;
57 | 60, 1.2];
58 | % Compare power consumption between nonlinear and linear model
59 | fprintf('----------------------------------------------------------------\n');
60 | fprintf('Comparison of nonlinear and linearized power consumption models\n');
61 | for i = 1:size(q_s_array, 1)
62 | q = q_s_array(i, 1);
63 | s = q_s_array(i, 2);
64 | P_nonlin = pump_power_consumption(test_pump, q, s, 1);
65 | P_lin = m_dq * q + m_ds * s + c;
66 | fprintf('Point %d : Flow %.1f , Speed %.2f , Power %.2f kW, perc. error %.2f , abs. error %.3f \n', ...
67 | i, q, s, P_nonlin, perc_error(P_nonlin, P_lin), abs_error(P_nonlin, P_lin));
68 | end
69 | fprintf('----------------------------------------------------------------\n');
70 | % Provide test pump data
71 | warning('on','all');
72 | fprintf('Test: %s complete. \n\n', test_name);
73 | y = 1;
74 | end
75 |
76 |
77 |
--------------------------------------------------------------------------------
/tests/matlab_octave/test_pump_linearization.m:
--------------------------------------------------------------------------------
1 | function y = test_pump_linearization()
2 | % Function for testing the accuracy of pump characteristic linearization
3 | test_name = 'test_pump_linearization';
4 | fprintf('\nRunning test: %s \n', test_name);
5 | warning('off','all');
6 | % Provide test pump data
7 | test_pump.smin = 0.7;
8 | test_pump.smax = 1.2;
9 | test_pump.max_eff_flow = 45;
10 | test_pump.A = -0.0045000;
11 | test_pump.B = 0;
12 | test_pump.C = 45;
13 |
14 | % Domain points
15 | % Specify domain vertices
16 | q_min = 0;
17 | s_min = test_pump.smin;
18 | s_max = test_pump.smax;
19 |
20 | q_int_smin = pump_intercept_flow(test_pump, 1, s_min);
21 | q_int_smax = pump_intercept_flow(test_pump, 1, s_max);
22 | % Use coordinates:
23 | % s
24 | % |
25 | % | *p2 *p3
26 | % |
27 | % | *p1 *p4
28 | % |______________q
29 | %
30 | p1 = [q_min, s_min, pump_head(test_pump, q_min, 1, s_min)]; % min-min
31 | p2 = [q_min, s_max, pump_head(test_pump, q_min, 1, s_max)]; % min-max
32 | p3 = [q_int_smax , s_max, pump_head(test_pump, q_int_smax, 1, s_max)]; % max-max
33 | p4 = [q_int_smin , s_min, pump_head(test_pump, q_int_smin, 1, s_min)]; % max-min
34 |
35 | % NOTE: The nominal point is derived from the informtion in the pump data structure
36 |
37 | % Linearize pump characteristic
38 | % NOTE: Specification of constraints is done in linearize_pump_characteristic
39 | % Move it outside when more than one linearization function support is
40 | % added
41 | linearized_test_pump_model = linearize_pump_characteristic(test_pump);
42 |
43 | % Pick operating points
44 | test_points = zeros(3,2);
45 | test_points(1,:) = [45, 1];
46 | test_points(2,:) = [20, 0.85];
47 | test_points(3,:) = [80, 1.1];
48 | selected_domains = [1, 1, 3];
49 | % Compare pump head from the non-linear and linear model
50 | % Nonlinear values
51 | fprintf('----------------------------------------------------------------\n');
52 | fprintf('Comparison of nonlinear and linearized pump models\n');
53 | for point_index = 1:size(test_points,1)
54 | q = test_points(point_index, 1);
55 | s = test_points(point_index, 2);
56 | H_nl = pump_head(test_pump, q, 1, s);
57 | H_l = pump_head_linear(linearized_test_pump_model, q, s, ...
58 | selected_domains(point_index));
59 | fprintf('Point %d : Flow %.1f , Speed %.2f , Head %.2f m, perc. error %.2f , abs. error %.3f \n', ...
60 | point_index, q, s, H_nl, perc_error(H_nl, H_l), abs_error(H_nl, H_l));
61 | end
62 | fprintf('----------------------------------------------------------------\n');
63 | warning('on','all');
64 | fprintf('Test: %s complete. \n\n', test_name);
65 | y = 1;
66 | end
67 |
68 |
69 |
--------------------------------------------------------------------------------
/tests/matlab_octave/test_rolling_out_vectors.m:
--------------------------------------------------------------------------------
1 | function y = test_rolling_out_vectors()
2 | %
3 | i_dim = 3;
4 | j_dim = 2;
5 | k_dim = 4;
6 |
7 | A = zeros(i_dim*j_dim*k_dim,1);
8 | b = zeros(k_dim, j_dim, i_dim);
9 |
10 | row = 1;
11 | for i = 1:i_dim
12 | for j = 1:j_dim
13 | for k = 1:k_dim
14 | A(row) = row;
15 | b(k,j,i) = row;
16 | row = row + 1;
17 | end
18 | end
19 | end
20 | b = b(:);
21 | if A == b
22 | y = 1;
23 | else
24 | y = 0;
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/tests/matlab_octave/test_var_intlinprog_vec_mapping.m:
--------------------------------------------------------------------------------
1 | function y = test_var_intlinprog_vec_mapping()
2 | % Tests the procedures for bi-directional mapping between var
3 | % structure elements and the decision variable vector.
4 | warning('off','all');
5 | test_name = 'test_var_intlinprog_vec_mapping';
6 | fprintf('\nRunning test: %s \n', test_name);
7 | % Initialize vars structure using the 2p1t network structure
8 | [input,const,linprog,network,sim,pump_groups] = initialise_2p1t();
9 | vars = initialise_var_structure(network, linprog);
10 | number_variables = vars.n_cont + vars.n_bin;
11 | rand_indices = randi([1 number_variables],1,100);
12 | for i = 1:length(rand_indices)
13 | var_index = rand_indices(i);
14 | % Map randomly generated index in the LP var vector to var structure
15 | y = map_lp_vector_index_to_var(vars, var_index);
16 | % Inverse map the calculated var struct access data back to the index in the LP var vector
17 | var_index2 = map_var_index_to_lp_vector(vars, y.subfield_name, num2cell(y.index));
18 | % Check if both indices are equal
19 | assert(isequal(var_index, var_index2), 'indices not equal');
20 | end
21 | fprintf('\nTest passed \n');
22 | y = 1;
23 | warning('on','all');
24 |
--------------------------------------------------------------------------------