├── .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 | ![pump_characteristic_linearization](https://user-images.githubusercontent.com/8837107/225595922-78017fe5-f952-4a8a-aa4c-a8c0cbbdf4b7.svg) 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 | --------------------------------------------------------------------------------