Repository: TAMUparametric/energiapy (language: python)

Issue:
cost plotting stacked bar plot
Added stacked bar plots:

they have  a log scale

Example: 

![bb11e83d-d1e7-480d-a67d-216ce6997266](https://user-images.githubusercontent.com/59067790/226633316-66780cc5-8dfa-461b-acda-864ba959e8cf.png)




Discussion / hints:

Fix patch:
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index f6f35d7b..a97b5c9a 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -35,6 +35,4 @@ jobs:
         flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
         # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
         flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
-    - name: Test with pytest
-      run: |
-        pytest
+
diff --git a/README.md b/README.md
index df52f60a..a75ca6f3 100644
--- a/README.md
+++ b/README.md
@@ -1,78 +1,97 @@
-# energiapy
-
-
-[![Documentation Status](https://readthedocs.org/projects/energiapy/badge/)](https://energiapy.readthedocs.io/en/latest/)
-
-
-energiapy (ενέργεια-py) is a tool for the multiscale modeling and optimization of energy systems. energiapy uses a resource task network (RTN) based approach to formulate mathematical programs as pyomo instances. Models can be solved using solvers, and the results can be analyzed within the energiapy framework. 
-Constituent functionalities have been built over the research conducted by the [Multiparameteric Optimization and Control (Pistikopoulos) Group](https://parametric.tamu.edu/).
-
-
-## Installation
-
-    pip install git+https://github.com/TAMUparametric/energiapy.git
-
-## Key Applications
-
-1. Design of future energy systems (network design)
-2. Scheduling under uncertainty
-3. Life-cycle and environmental impact assessment
-4. Techno-economic analysis 
-5. System resiliency and reliability characterization
-
-Models can also simultaneously assuage the above under an integrated paradigm.
-
-## Modeling Approach
-
-Model components are declared as energiapy objects. The suggested hierarchy is as follows:
-
-1. **Temporal_scale:** The multiple temporal scales being considered
-2. **Resource:** Resources can be in any quantifiable form such mass, energy, information
-3. **Material:** Infrastructural materials are required for establishing tasks, and require resources if produced 
-4. **Process:** Tasks/processes are set up by utilizing materials, and convert resources from on form to the other.
-5. **Location:** Locations are essentially a set of process. Capacity, cost, and demand variability can also be introduced.
-6. **Transport:** Modes of transportation translocate resources. Materials usage can also be provided.
-7. **Network:** Networks connect locations using transport linkages. The distance and transport availability matrices need to be provided. 
-8. **Scenario:** single location (using Location) or multi-location (using Network) scenarios can be generated at appropriate scales.
-
-Scenarios can then be formulated as a pyomo instance with the formulate function by using a set of constraints, setting an objective, providing demand targets, etc. Sets and variables are defined implicitly. Bespoke constraints can be added using the pyomo syntax. The solve functionality using appropriate solvers provides a solution (Result), which can be exported, analyzed, or itself used to initialize models. 
-
-## Select Features
-
-Available constraints are able to model:
-
-1. network design (using binaries)
-2. resource flows
-3. inventory balance
-4. costing
-5. emission (using global warming potential)
-6. land use
-7. mode based production (multiple resource inputs, nonlinear behavior modeling using piece-wise linear curves)
-8. transportation
-9. failure rates
-
-A model can be optimized towards:
-
-1. minimizing cost
-2. minimizing emission
-3. maximizing resource discharge
-
-Large scenarios can be aggregated using the following available techniques:
-
-1. agglomerative hierarchial clustering (AHC)
-2. dynamic time warping (DTW)
-
-Both the input data and solution output can be plotted with energiapy's own plot module:
-
-1. input data: capacity, demand, cost factors
-2. solution output: inventory, production, consumption, discharge/sales schedule; contribution towards costs (capital, variable and fixed operational), meeting demand.
-
-Other (and optional) features include:
-
-1. math utilities for calculating euclidean distances, generation connectivity matrices, etc.
-2. generating dataframes with missing data fixed (weekends, public holidays) for time-series data such as resource price
-3. function to fetch weather data at an appropriate resolution from [NREL NSRDB](https://nsrdb.nrel.gov/) for any county in the US
-4. latex constraints writer for model documentation
-
-
-
+# energiapy
+
+[![Documentation Status](https://readthedocs.org/projects/energiapy/badge/)](https://energiapy.readthedocs.io/en/latest/)
+[![PyPI](https://img.shields.io/pypi/v/energiapy.svg)](https://pypi.org/project/energiapy)
+[![Downloads](https://static.pepy.tech/personalized-badge/energiapy?period=total&units=international_system&left_color=grey&right_color=orange&left_text=Downloads)](https://pepy.tech/project/energiapy)
+
+
+energiapy is a tool for the multiscale modeling and optimization of energy systems. energiapy uses a resource task network (RTN) based approach to formulate mathematical programs as pyomo instances. Models can be solved using solvers, and the results can be analyzed within the energiapy framework. 
+Constituent functionalities have been built over the research conducted by the [Multiparameteric Optimization and Control (Pistikopoulos) Group](https://parametric.tamu.edu/).
+
+
+## Installation
+
+energiapy can be installed using the standard pip installation. It is recommended to install the package in a separate conda or pip environment. 
+
+Create and activate a conda environment 
+
+    conda create --name energiaenv python=3.10
+    conda activate energiaenv
+
+Or, create and activate a pip environment 
+
+    python3.10 -m venv energiaenv
+    .\energiaenv\Scripts\activate
+
+Install energiapy
+
+    pip install energiapy
+
+energiapy is being developed as a PhD project, and is hence frequently updated. For the most updated (sometimes unstable) version use
+
+    pip install git+https://github.com/TAMUparametric/energiapy
+
+## Key Applications
+
+1. Design of future energy systems (network design)
+2. Scheduling under uncertainty
+3. Life-cycle and environmental impact assessment
+4. Techno-economic analysis 
+5. System resiliency and reliability characterization
+
+Models can also simultaneously assuage the above under an integrated paradigm.
+
+## Modeling Approach
+
+Model components are declared as energiapy objects. The suggested hierarchy is as follows:
+
+1. **Temporal_scale:** The multiple temporal scales being considered
+2. **Resource:** Resources can be in any quantifiable form such mass, energy, information
+3. **Material:** Infrastructural materials are required for establishing tasks, and require resources if produced 
+4. **Process:** Tasks/processes are set up by utilizing materials, and convert resources from on form to the other.
+5. **Location:** Locations are essentially a set of process. Capacity, cost, and demand variability can also be introduced.
+6. **Transport:** Modes of transportation translocate resources. Materials usage can also be provided.
+7. **Network:** Networks connect locations using transport linkages. The distance and transport availability matrices need to be provided. 
+8. **Scenario:** single location (using Location) or multi-location (using Network) scenarios can be generated at appropriate scales.
+
+Scenarios can then be formulated as a pyomo instance with the formulate function by using a set of constraints, setting an objective, providing demand targets, etc. Sets and variables are defined implicitly. Bespoke constraints can be added using the pyomo syntax. The solve functionality using appropriate solvers provides a solution (Result), which can be exported, analyzed, or itself used to initialize models. 
+
+## Select Features
+
+Available constraints are able to model:
+
+1. network design (using binaries)
+2. resource flows
+3. inventory balance
+4. costing
+5. emission (using global warming potential)
+6. land use
+7. mode based production (multiple resource inputs, nonlinear behavior modeling using piece-wise linear curves)
+8. transportation
+9. failure rates
+
+A model can be optimized towards:
+
+1. minimizing cost
+2. minimizing emission
+3. maximizing resource discharge
+
+Large scenarios can be aggregated using the following available techniques:
+
+1. agglomerative hierarchial clustering (AHC)
+2. dynamic time warping (DTW)
+
+Both the input data and solution output can be plotted with energiapy's own plot module:
+
+1. input data: capacity, demand, cost factors
+2. solution output: inventory, production, consumption, discharge/sales schedule; contribution towards costs (capital, variable and fixed operational), meeting demand.
+
+Other (and optional) features include:
+
+1. math utilities for calculating euclidean distances, generation connectivity matrices, etc.
+2. generating dataframes with missing data fixed (weekends, public holidays) for time-series data such as resource price
+3. function to fetch weather data at an appropriate resolution from [NREL NSRDB](https://nsrdb.nrel.gov/) for any county in the US
+4. latex constraints writer for model documentation
+
+
+
diff --git a/conf.py b/conf.py
new file mode 100644
index 00000000..06d75283
--- /dev/null
+++ b/conf.py
@@ -0,0 +1,28 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+project = 'energiapy'
+copyright = '2023, Rahul Kakodkar'
+author = 'Rahul Kakodkar'
+release = '1.0.4'
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+extensions = []
+
+templates_path = ['_templates']
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+html_theme = 'alabaster'
+html_static_path = []
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index d4bb2cbb..00000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,20 +0,0 @@
-# Minimal makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line, and also
-# from the environment for the first two.
-SPHINXOPTS    ?=
-SPHINXBUILD   ?= sphinx-build
-SOURCEDIR     = .
-BUILDDIR      = _build
-
-# Put it first so that "make" without argument is like "make help".
-help:
-	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-
-.PHONY: help Makefile
-
-# Catch-all target: route all unknown targets to Sphinx using the new
-# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile
-	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/conf.py b/docs/conf.py
index d57e34a6..85958e91 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -22,7 +22,7 @@
 project = 'energiapy'
 copyright = '2023, Rahul Kakodkar'
 author = 'Rahul Kakodkar'
-release = '1.0.0'
+release = '1.0.4'
 
 # -- General configuration ---------------------------------------------------
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
@@ -38,5 +38,5 @@
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
 
 html_theme = 'sphinx_rtd_theme'
-html_static_path = ['_static']
-source_suffix = [".rst", ".md"]
+html_static_path = ['static_']
+source_suffix = [".rst", ".md"]
\ No newline at end of file
diff --git a/docs/energiapy.aggregation.rst b/docs/energiapy.aggregation.rst
new file mode 100644
index 00000000..0f56d946
--- /dev/null
+++ b/docs/energiapy.aggregation.rst
@@ -0,0 +1,37 @@
+energiapy.aggregation package
+=============================
+
+Submodules
+----------
+
+energiapy.aggregation.ahc module
+--------------------------------
+
+.. automodule:: energiapy.aggregation.ahc
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+energiapy.aggregation.dtw module
+--------------------------------
+
+.. automodule:: energiapy.aggregation.dtw
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+energiapy.aggregation.reduce\_scenario module
+---------------------------------------------
+
+.. automodule:: energiapy.aggregation.reduce_scenario
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: energiapy.aggregation
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/energiapy.model.constraints.rst b/docs/energiapy.model.constraints.rst
index eb3bb4ca..949c1327 100644
--- a/docs/energiapy.model.constraints.rst
+++ b/docs/energiapy.model.constraints.rst
@@ -28,6 +28,14 @@ energiapy.model.constraints.failure module
    :undoc-members:
    :show-inheritance:
 
+energiapy.model.constraints.integer\_cuts module
+------------------------------------------------
+
+.. automodule:: energiapy.model.constraints.integer_cuts
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
 energiapy.model.constraints.inventory module
 --------------------------------------------
 
diff --git a/docs/energiapy.rst b/docs/energiapy.rst
index 09b95353..9a087d15 100644
--- a/docs/energiapy.rst
+++ b/docs/energiapy.rst
@@ -7,6 +7,7 @@ Subpackages
 .. toctree::
    :maxdepth: 4
 
+   energiapy.aggregation
    energiapy.components
    energiapy.graph
    energiapy.model
diff --git a/docs/example_ahc.rst b/docs/example_ahc.rst
new file mode 100644
index 00000000..41aac651
--- /dev/null
+++ b/docs/example_ahc.rst
@@ -0,0 +1,194 @@
+Agglomerative Hierarchial Clustering (AHC) example
+==================================================
+
+In this example, we highlight the scenario aggregation capabilities of energiapy
+
+Consider the example stated in the *Small Energy Systems MILP Example*. 
+
+However, let us use data the following data for Houston:
+
+- demand data for power at an hourly resolution from ERCOT
+- solar and wind capacities at an hourly resolution from NREL NSRDB
+
+The temporal scale in its entirety has 8760 time periods
+
+The problem can be modeled over three scales
+
+- 0, network scale with 1 time period where the network decisions such as setting up a unit are taken
+- 1, a daily scale for convenience with 365 time periods, if the demand is to be met on a daily basis 
+- 2, scheduling and demand scales with 8760 time periods, where the resource flows are determined
+
+Given the large problem size, the model can be aggregated using Agglomerative Hierarchial Clustering (AHC) which provides
+a set of representative days over which the problem can be solve. Understandably, there is a loss of model accuracy.
+
+**Import modules**
+
+.. code-block:: python
+
+    import pandas 
+    import numpy
+    from energiapy.components.temporal_scale import Temporal_scale
+    from energiapy.components.resource import Resource, VaryingResource
+    from energiapy.components.process import Process, VaryingProcess
+    from energiapy.components.material import Material
+    from energiapy.components.location import Location
+    from energiapy.components.scenario import Scenario
+    from energiapy.components.result import Result 
+    from energiapy.model.formulate import formulate, Constraints, Objective
+    from energiapy.utils.nsrdb_utils import fetch_nsrdb_data
+    from energiapy.plot import plot
+    from energiapy.model.solve import solve
+    from energiapy.aggregation.ahc import agg_hierarchial, agg_hierarchial_elbow, IncludeAHC, Fit
+    from energiapy.aggregation.reduce_scenario import reduce_scenario, Clustermethod
+    from energiapy.utils.data_utils import load_results
+
+
+**Declare temporal scale**
+
+With 1 year, 365 days, and 24 hours in each day. 
+
+.. code-block:: python
+    
+    scales = Temporal_scale(discretization_list= [1, 365, 24])
+
+**Declare resources and processes**
+
+This is the same problem as shown in *Small Energy Systems MILP Example*. 
+
+.. code-block:: python
+
+    Solar = Resource(name='Solar', cons_max=100, basis='MW', label='Solar Power')
+
+    Wind = Resource(name='Wind', cons_max= 100, basis='MW', label='Wind Power')
+
+    Power = Resource(name='Power', basis='MW', demand = True, label='Power generated', varying = VaryingResource.deterministic_demand)
+
+    LiI = Process(name='LiI', storage= Power, capex = 1302182, fopex= 41432, vopex = 2000,  store_max=1000, label='Lithium-ion battery', basis = 'MW')
+
+    WF = Process(name='WF', conversion={Wind: -1, Power: 1},capex=990637, fopex=3354, vopex=4953, prod_max=100, label='Wind mill array', varying= VaryingProcess.deterministic_capacity, basis = 'MW')
+
+    PV = Process(name='PV', conversion={Solar: -1, Power: 1}, capex=567000, fopex=872046, vopex=90000, prod_max=100, varying = VaryingProcess.deterministic_capacity, label = 'Solar PV', basis = 'MW')
+
+**Import location level data**
+
+The solar and wind availability data for Houston can be fetched using the fetch_nsrdb_data function.
+
+.. code-block:: python 
+
+    weather_houston =  fetch_nsrdb_data(attrs = ['wind_speed', 'dni'], year = 2019, state = 'Texas', county = 'Harris',\
+        resolution= 'hourly', get = 'min-elevation', save = 'data/ho_solar19')[1] 
+
+The demand data for Houston can be downloaded for the ERCOT website
+
+.. code-block:: python 
+
+    demand_houston = pandas.read_excel('data/Native_Load_2019.xlsx')[['COAST']]
+    demand_houston = demand_houston.set_index(weather_houston.index)
+
+**Declare Location**
+
+Here we declare a Location 'houston' with embedded hourly data for wind and solar availability, and power demand
+
+
+.. code-block:: python 
+
+    houston = Location(name='HO', processes= {LiI, PV, WF}, capacity_factor = {PV: pandas.DataFrame(weather_houston['dni']), \
+    WF: pandas.DataFrame(weather_houston['wind_speed'])}, demand_factor = {Power: pandas.DataFrame(demand_houston)}, scales=scales, label='Houston', demand_scale_level=2, \
+        capacity_scale_level= 2, cost_scale_level= 1)
+
+
+**Plotting input data**
+
+The input data can be plotted using the native plotting function plot for capacity_factor and demand_factor.
+
+.. image:: multi_loc_wf.png
+
+.. image:: multi_loc_dem.png
+
+
+
+**Generate the full-scale MILP**
+
+We include constraints for cost, inventory, production, and resource_balance.
+
+.. code-block:: python 
+
+    milp = formulate(scenario= scenario, demand = {houston: {Power: 2}}, constraints={Constraints.cost, Constraints.inventory, Constraints.production, Constraints.resource_balance}, \
+            objective=Objective.cost)
+ 
+
+**Solve the full-scale model**
+
+We solve the generated MILP using the Gurobi solver.
+
+.. code-block:: python 
+
+    results = solve(scenario = scenario, instance= milp, solver= 'gurobi', name=f"results", print_solversteps = True)
+
+**Determining the optimal cluster size**
+
+The within cluster sum of squares (WCSS) are a good indication of the error in clustering. We include capacity, and demand factors in the include field.
+The elbow point can be determine as shown here. 
+
+First, determine the WCSS for a reasonable range of clusters
+
+.. code-block:: python
+
+    wcss = agg_hierarchial_elbow(scenario= scenario, scale_level=1, include= [IncludeAHC.capacity, IncludeAHC.demand], range_list = list(range(30,120)))
+
+
+Next, we apply Multivariate Adapative Regression Splines (MARS) to determine the elbow point 
+
+
+.. image:: mars.png
+
+**Generate a reduced scenario**
+
+The reduce_scenario function provides a reduced scenario, the set of representative days and some related information, such as the total WCSS.
+
+.. code-block:: python
+
+    scenario_reduced, repdays, info =  reduce_scenario(scenario=scenario, periods=elbow, scale_level=1, method=Clustermethod.agg_hierarchial, include = [IncludeAHC.capacity, IncludeAHC.demand])
+
+**Formulate the reduced MILP**
+
+.. code-block:: python
+
+    milp_reduced = formulate(scenario= scenario_reduced, demand = { houston: {Power: 2}}, constraints={Constraints.cost, Constraints.inventory, Constraints.production, Constraints.resource_balance}, \
+        objective=Objective.cost)
+
+**Solve the reduced scenario**
+The reduced scenario can then be solved, and the error can be ascertained.
+
+.. code-block:: python
+
+    results_reduced = solve(scenario = scenario_reduced, instance= milp_reduced, solver= 'gurobi', name=f"reduced_res", print_solversteps = True)
+    
+    error = (results.output['objective'] - results_reduced.output['objective'])*100/results.output['objective']
+
+
+**Plotting results**
+
+Native plotting functions can be used to plot results such as the optimal schedule and capital utilization as shown. 
+Here, we are plotting production schedule for both the full scale as well as the reduced scenario.
+
+.. code-block:: python 
+
+    plot.schedule(results = results, location='HO', component='WF', y_axis= 'P')
+    plot.schedule(results = results_reduced, location='HO', component='WF', y_axis= 'P')
+
+
+.. image:: sch_full_wf.png
+
+.. image:: sch_red_wf.png
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/example_mpLP.rst b/docs/example_mpLP.rst
new file mode 100644
index 00000000..4371297b
--- /dev/null
+++ b/docs/example_mpLP.rst
@@ -0,0 +1,329 @@
+Reformulating an energy systems MILP as a mpLP 
+==============================================
+
+In this example a simple multi-scale energy systems MILP is reformulated as a single-period mpLP
+
+**Nomenclature**
+
+The sets and variables used are stated here
+
+*Sets*
+
+
+- R - set of all resources r
+- P - set of all processes p
+- T - set of temporal periods p
+
+
+*Subsets*
+
+- R\ :sup:`storage` - set of resources that can be stored
+- R\ :sup:`sell` - set of resources that can be discharged
+- R\ :sup:`demand` - set of resources that meet  demand
+- R\ :sup:`cons` - set of resources that can be consumed
+- P\ :sup:`uncertain-cap` - set of processes with uncertain capacity
+- P\ :sup:`uncertain-capex` - set of processes with uncertain capital expenditure
+- T - set of temporal periods 
+- T\ :sup:`net` - set of temporal periods t for network level decision making
+- T\ :sup:`sch` - set of temporal periods t for schedule level decision making
+
+
+*Continuous Variables*
+
+
+- P\ :sub:`p,t` - production level of p :math:`{\in}`  P in time period t :math:`{\in}` T\ :sup:`sch`  
+    
+- C\ :sub:`r,t` - consumption of r :math:`{\in}` R\ :sup:`cons` time period t :math:`{\in}` T\ :sup:`sch` 
+    
+- S\ :sub:`r,t` - discharge of r :math:`{\in}` R\ :sup:`demand` time period t :math:`{\in}` T\ :sup:`sch` 
+    
+- Inv\ :sub:`r,t` - inventory level of r :math:`{\in}` R\ :sup:`storage`  in time period t :math:`{\in}` T\ :sup:`sch`
+    
+- Cap\ :sup:`S` \ :sub:`r,t` - installed inventory capacity for resource r :math:`{\in}`  R\ :sup:`storage` in time period t :math:`{\in}` T\ :sup:`net`
+    
+- Cap\ :sup:`P` \ :sub:`p,t` - installed production capacity for process p :math:`{\in}` P in time period t :math:`{\in}` T\ :sup:`net`
+    
+
+
+
+*Binary Variables*
+
+
+- X\ :sup:`P` \ :sub:`p,t` - network binary for production process p :math:`{\in}` P in time period t :math:`{\in}` T\ :sup:`net`
+- X\ :sup:`S` \ :sub:`r,t` - network binary for inventory of resource r :math:`{\in}` R\ :sup:`storage` in time period t :math:`{\in}` T\ :sup:`net`
+
+
+
+*Parametric Variables*
+
+
+- :math:`{\alpha}` \ :sub:`p` - uncertainty in production capacity of process p :math:`{\in}` P\ :sup:`uncertain-cap`
+- :math:`{\beta}` \ :sub:`r` - uncertainty in demand for resource r :math:`{\in}` R\ :sup:`demand`
+- :math:`{\gamma}` \ :sub:`r` - uncertainty in purchase price for resource r :math:`{\in}` R\ :sup:`cons`
+- :math:`{\delta}` \ :sub:`r` - uncertainty in consumption availability for resource r :math:`{\in}` R\ :sup:`demand`
+- :math:`{\epsilon}` \ :sub:`p` - uncertainty in the cost of technology for p :math:`{\in}` P\ :sup:`uncertain-capex`
+
+*Parameters*
+
+
+- Cap\ :sup:`P-max` \ :sub:`p,t` - maximum production capacity of process p :math:`{\in}` P in time period t :math:`{\in}` T\ :sup:`net`
+- Cap\ :sup:`S-max` \ :sub:`r,t` - maximum inventory capacity for process r :math:`{\in}` R\ :sup:`storage` in time period t :math:`{\in}` T\ :sup:`net`
+- Capex\ :sub:`p,t` - capital expenditure for process p :math:`{\in}` P in time t :math:`{\in}` T\ :sup:`net`
+- Price\ :sub:`r,t` - purchase price for resource r :math:`{\in}` R\ :sup:`cons` in time t :math:`{\in}` T\ :sup:`sch`
+- C\ :sup:`max` \ :sub:`r,t` - maximum consumption availability for resource r :math:`{\in}` R\ :sup:`cons` in time t :math:`{\in}` T\ :sup:`sch`
+- D\ :sub:`r,t` - demand for resource r in R\ :sup:`sell` in time t :math:`{\in}` T\ :sup:`sch`
+
+**MILP Formulation**
+
+Given is a general MILP modeling and optimization framework for simultaneous network design and scheduling.
+
+
+.. math::
+    \begin{equation}
+        min \sum_{t \in \mathcal{T}^{net}} \sum_{p \in \mathcal{P}} Capex_{p,t} \times Cap^P_{p,t} + \sum_{t \in \mathcal{T}^{sch}} \sum_{p \in \mathcal{P}}  Vopex_{r,t} \times P_{r,t} + \sum_{t \in \mathcal{T}^{sch}} \sum_{r \in \mathcal{R}^{cons}}  Price_{r,t}  \times C_{r,t} 
+    \end{equation}
+
+
+.. math::
+    \begin{equation}
+        Cap^S_{r,t} \leq Cap^{S-max}_{r,t} \times X^S_{r,t} \hspace{1cm} \forall r \in \mathcal{R}^{storage}, t \in \mathcal{T}^{net}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        Cap^P_{p,t} \leq Cap^{P-max}_{p,t} \times X^P_p  \hspace{1cm} \forall p \in \mathcal{P}, t \in \mathcal{T}^{net}
+    \end{equation} 
+
+.. math::
+    \begin{equation}
+        P_{p,t} \leq Cap^{P}_{p,t}  \hspace{1cm} \forall p \in \mathcal{P}, t \in \mathcal{T}^{sch}
+    \end{equation} 
+
+.. math::
+    \begin{equation}
+        Inv_{r,t} \leq Cap^{S}_{r,t}  \hspace{1cm} \forall r \in \mathcal{R}^{storage}, t \in \mathcal{T}^{sch}
+    \end{equation} 
+
+.. math::
+    \begin{equation}
+        - S_{r,t} \leq - D_{r,t}  \hspace{1cm} \forall r \in \mathcal{R}, t \in \mathcal{T}^{sch}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        C_{r,t} \leq C^{max}_{r,t} \hspace{1cm} \forall r \in \mathcal{R}, t \in \mathcal{T}^{sch}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        - S_{r,t} + \sum_{p \in \mathcal{P}} P_{p,t} \times \eta(p,r) = 0 \hspace{1cm} \forall r \in \mathcal{R}^{sell}, t \in \mathcal{T}^{sch}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        -Inv_{r,t} + \sum_{p \in \mathcal{P}} P_{p,t} \times \eta(p,r) = 0 \hspace{1cm} \forall r \in \mathcal{R}^{stored}, t \in \mathcal{T}^{sch}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        \sum_{p \in \mathcal{P}} P_{p,t} \times \eta(p,r) + C_{r,t} = 0 \hspace{1cm} \forall r \in \mathcal{R}^{cons}, t \in \mathcal{T}^{sch}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        S_{r,t}, C_{r,t}, Inv_{r,t}, P_{p,t}, Cap^P_p, Cap^S_r \in R_{\geq 0}
+    \end{equation}
+
+**mpLP**
+
+Reformulated, a general mpLP for the above MILP will looks something like this:
+
+.. math::
+    \begin{equation}
+        min \hspace{1cm} \sum_{p \in \mathcal{P}} Capex_p \times \epsilon_p \times P_p + \sum_{r \in \mathcal{R}^{cons}} C_r \times \gamma_r 
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        Inv_r \leq Cap^{S-max}_r \hspace{1cm} \forall r \in \mathcal{R}^{stored}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        - S_r \leq - D_r \times \beta_r \hspace{1cm} \forall r \in \mathcal{R}^{demand}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        C_r \leq C^{max}_r \times \delta_r \hspace{1cm} \forall r \in \mathcal{R}^{cons} 
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        P_p \leq Cap^{P-max}_p \times \alpha_p \hspace{1cm} \forall p \in \mathcal{P}
+    \end{equation} 
+
+.. math::
+    \begin{equation}
+        - S_{r} + \sum_{p \in \mathcal{P}} P_{p} \times \eta(p,r) = 0 \hspace{1cm} \forall r \in \mathcal{R}^{sell}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        - Inv_{r} + \sum_{p \in \mathcal{P}} P_{p} \times \eta(p,r) = 0 \hspace{1cm} \forall r \in \mathcal{R}^{stored}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        \sum_{p \in \mathcal{P}} P_{p} \times \eta(p,r) + C_{r} = 0 \hspace{1cm} \forall r \in \mathcal{R}^{cons}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        \alpha_p \in A_p \hspace{1cm} \forall p \in \mathcal{P}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        \beta_r \in B_r \hspace{1cm} \forall r \in \mathcal{R}^{demand}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        \gamma_r \in \Gamma_r \hspace{1cm} \forall r \in \mathcal{R}^{cons}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        \delta_r \in \Delta_r \hspace{1cm} \forall r \in \mathcal{R}^{cons}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        \epsilon_p \in E_p \hspace{1cm} \forall p \in \mathcal{P}
+    \end{equation}
+
+
+.. math::
+    \begin{equation}
+        S_r, C_r, Inv_r, P_p \in R_{\geq 0}
+    \end{equation}
+
+**Example energiapy implementation**
+
+Let us now look at an example problem
+
+
+.. math::
+    \begin{equation}
+        p \in \{LI_c, LI_d, WF, PV\} 
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        r \in \{charge, power, wind, solar\} 
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        min \hspace{1cm} \left[\begin{matrix}1302\\0\\990\\567\end{matrix}\right]^T \left[\begin{matrix}P_{LI_c}\\P_{LI_d}\\P_{WF}\\P_{PV}\end{matrix}\right]
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        I_3\left[\begin{matrix}Inv_{charge}\\C_{wind}\\C_{solar}\\P_{LI_c}\\P_{LI_d}\end{matrix}\right] \leq \left[\begin{matrix} 100\\100\\100\\100\\100\end{matrix}\right]
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        I_3\left[\begin{matrix}-S_{power}\\P_{WF}\\P_{PV}\end{matrix}\right] \leq \left[\begin{matrix}-300 & 0 & 0\\0 & 100 & 0\\0 & 0 & 100\end{matrix}\right] \left[\begin{matrix}\beta_{power}\\ \alpha_{WF}\\\alpha_{PV}\end{matrix}\right]
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        I_4\left[\begin{matrix} - Inv_{charge} \\ -S_{power} \\ C_{wind} \\ C_{solar} \end{matrix}\right] + \left[\begin{matrix}0.89 & -1 & 0 & 0\\-1 & 1 & 0.85 & 0.75\\0 & 0 & -1 & 0\\0 & 0 & 0 & -1\end{matrix}\right] \left[\begin{matrix}P_{LI_c}\\P_{LI_d}\\P_{WF}\\P_{PV}\end{matrix}\right] = 0
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        \alpha_p \in \mathcal{A}_p \hspace{1cm} \forall p \in \{WF, PV\} 
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        \beta_r \in \mathcal{B}_r \hspace{1cm} \forall r \in \{Power\} 
+    \end{equation}
+
+
+
+**Example energiapy implementation**
+
+Let us now look at an example problem
+
+
+Now we are ready to implement the above problem in energiapy
+
+**Import modules**
+
+.. code-block:: python
+
+    from energiapy.components.temporal_scale import Temporal_scale
+    from energiapy.components.resource import Resource, VaryingResource
+    from energiapy.components.process import Process, VaryingProcess
+    from energiapy.components.location import Location
+    from energiapy.components.scenario import Scenario
+    from energiapy.components.result import Result 
+    from energiapy.model.formulate import formulate, Constraints, Objective
+    from energiapy.plot import plot
+    from energiapy.model.solve import solve
+    import numpy
+    from ppopt.mpqp_program import MPQP_Program
+    from ppopt.mplp_program import MPLP_Program
+    from ppopt.mp_solvers.solve_mpqp import solve_mpqp, mpqp_algorithm
+    from ppopt.plot import parametric_plot
+
+**Define the temporal scale**
+
+In the mpLP example, instead of using multi-scale indices, the problem can be formulated in a single period.
+
+.. code-block:: python
+
+    scales = Temporal_scale(discretization_list=[1])
+
+**Declare the problem scenario**
+
+.. code-block:: python
+
+    Solar = Resource(name='Solar', cons_max=100, basis='MW', label='Solar Power')
+
+    Wind = Resource(name='Wind', cons_max= 100, basis='MW', label='Wind Power')
+
+    Power = Resource(name='Power', basis='MW', demand = True, label='Power generated', varying= VaryingResource.uncertain_demand)
+
+    LiI = Process(name='LiI', storage= Power, capex = 1302, fopex= 0, vopex = 0,  prod_max=50, storage_loss = 0.11,  label='Lithium-ion battery', basis = 'MW')
+
+    WF = Process(name='WF', conversion={Wind: -1, Power: 0.85},capex=990, fopex= 0, vopex=0, prod_max=100, label='Wind mill array', basis = 'MW', varying =  VaryingProcess.uncertain_capacity)
+
+    PV = Process(name='PV', conversion={Solar: -1, Power: 0.75}, capex=567, fopex=0, vopex=0, prod_max=100, label = 'Solar PV', basis = 'MW', varying = VaryingProcess.uncertain_capacity)
+    
+    region = Location(name='region', processes= {LiI, PV, WF}, scales=scales, label='some region')
+    
+    example = Scenario(name= 'example', demand = {region: {Power: 100}}, network= region, scales= scales, label= 'example scenario')
+
+
+**Generate the problem in matrix form**
+
+.. code-block:: python
+
+    A, b, c, H, CRa, CRb, F  = example.matrix_form()
+
+
+**Solve the problem using the ppopt solver**
+
+.. code-block:: python
+
+    prog = MPLP_Program(A, b, c, H, CRa, CRb, F, equality_indices=[0,1,2,3])
+    prog.solver.solvers['lp'] = 'gurobi'
+    prog.warnings()
+    prog.display_warnings()
+    solution1 = solve_mpqp(prog, mpqp_algorithm.combinatorial)
diff --git a/docs/example_multiloc.rst b/docs/example_multiloc.rst
new file mode 100644
index 00000000..3e1385e5
--- /dev/null
+++ b/docs/example_multiloc.rst
@@ -0,0 +1,438 @@
+Multi-location Energy Systems MILP Example
+==========================================
+
+This is an example implementation of the energiapy python module for a problem instance with multiple locations
+
+energiapy relies on the resource task network (RTN) approach:
+
+In the RTN representation, systems can be visualized as transitional elements which convert resources into other resources.
+The distinction between and energy and material resources is merely illustrative. Mathematically, resources are treated consistently throughout the framework
+
+The key goals are to:
+
+1. Identify synergies between material and energy supply chains 
+2. Propose network designs for future energy systems
+3. Perform scenario analysis to identify critical components
+
+The systems modeled in the example case study include:
+
+* dense energy carrier (DEC) production; e.g. hydrogen through electrolysis and methane reforming, production of methanol through a novel catalytic process
+* carbon capture, utilization, and sequestration; e.g. direct air capture (DAC), sequestration in aquifers, utilization to produce methanol  
+* power generation; e.g. solar, wind, natural gas
+* transportation networks; e.g. trains and pipeline for the transport of energy vectors.
+
+.. image:: multiloc_im.png
+
+
+**Nomenclature**
+
+*Sets*
+
+
+- R - set of all resources r
+- P - set of all processes p
+- T - set of temporal periods t
+- B - set of transport modes b
+- L - set of locations l
+
+
+*Subsets*
+
+
+- R\ :sup:`storage` - set of resources that can be stored
+- R\ :sup:`sell` - set of resources that can be discharged
+- R\ :sup:`demand` - set of resources that meet  demand
+- R\ :sup:`cons` - set of resources that can be consumed
+- P\ :sup:`uncertain` - set of processes with uncertain capacity
+- R\ :sup:`trans` - set of resources that can be transported
+- T\ :sup:`sch` - set of temporal periods t for scheduling decisions
+- T\ :sup:`net`- set of temporal periods t for network decisions
+- L\ :sup:`source` - set of source locations 
+- L\ :sup:`sink` - set of sink locations 
+
+
+
+
+*Continuous Variables*
+
+
+- P\ :sub:`l,p,t`- production level of p :math:`{\in}`  P in time period t :math:`{\in}` T\ :sup:`sch`  
+
+- C\ :sub:`l,r,t`- consumption of r :math:`{\in}` in R\ :sup:`cons` time period t :math:`{\in}` T\ :sup:`sch` 
+
+- S\ :sub:`l,r,t`- discharge of r :math:`{\in}` in R\ :sup:`demand` time period t :math:`{\in}` T\ :sup:`sch` 
+
+- Inv\ :sub:`l,r,t`- inventory level of r :math:`{\in}` R\ :sup:`storage`  in time period t :math:`{\in}` T\ :sup:`sch` 
+
+- Cap\ :sup:`S` \ :sub:`l,r,t` - installed inventory capacity for resource r :math:`{\in}`  R\ :sup:`storage` in time period t :math:`{\in}` T\ :sup:`net` 
+
+- Cap\ :sup:`P` \ :sub:`l,p,t` - installed production capacity for process p :math:`{\in}` P in time period t :math:`{\in}` T\ :sup:`net` 
+
+- Trans\ :sup:`out` \ :sub:`b,l,l',p,t` - amount of r :math:`{\in}` R through mode b :math:`{\in}` B in time period t :math:`{\in}` T\ :sup:`net` 
+
+- Trans\ :sup:`in` \ :sub:`b,l,l',p,t` - amount of r :math:`{\in}` R through mode b :math:`{\in}` B in time period t :math:`{\in}` T\ :sup:`net` 
+
+
+
+*Binary Variables*
+
+
+- X\ :sup:`P` \ :sub:`l,p,t` - network binary for production process p :math:`{\in}` P
+- X\ :sup:`S` \ :sub:`l,r,t` - network binary for inventory of resource r :math:`{\in}`  R\ :sup:`storage` 
+- X\ :sup:`B` \ :sub:`b,l,l',r,t` - binary for transport of resource r :math:`{\in}` R\ :sup:`trans`   between locations
+
+
+
+*Parameters*
+
+
+- Cap\ :sup:`P-max` \ :sub:`l,p,t` - maximum production capacity of process p :math:`{\in}` P in time t :math:`{\in}` T\ :sup:`net`
+- Cap\ :sup:`S-max` \ :sub:`l,r,t` - maximum inventory capacity for process r :math:`{\in}` R\ :sup:`storage` in time t :math:`{\in}` T\ :sup:`net`
+- Capex\ :sub:`l,p,t` - capital expenditure for process p :math:`{\in}` P in time t :math:`{\in}` T\ :sup:`net`
+- Vopex\ :sub:`l,p,t` - variable operational expenditure for process p :math:`{\in}` P in time t :math:`{\in}` T\ :sup:`sch`
+- Price\ :sub:`l,r,t` - purchase price for resource r :math:`{\in}` R\ :sup:`cons` in time t :math:`{\in}` T\ :sup:`sch`
+- C\ :sup:`max` \ :sub:`l,r,t` - maximum consumption availability for resource r :math:`{\in}` R\ :sup:`cons` in time t :math:`{\in}` T\ :sup:`sch`
+- D\ :sub:`l,r,t` - demand for resource r $in$ R\ :sup:`sell` in time t :math:`{\in}` T\ :sup:`sch`
+- Trans\ :sup:`cost` \ :sub:`b,r,t- transport cost for resource r $in$ R\ :sup:`trans` in time t :math:`{\in}` T\ :sup:`sch` for mode b :math:`{\in}` B
+
+**MILP Formulation**
+
+A multi-location, multi-product energy system model can be formulated as shown here 
+
+
+
+.. math::
+    \begin{equation}
+        min \sum_{l \in \mathcal{L}} \Big(\sum_{t \in \mathcal{T}^{net}} \sum_{p \in \mathcal{P}} Capex_{l,p,t} \times Cap^P_{l,p,t} +  \sum_{t \in \mathcal{T}^{sch}} \sum_{p \in \mathcal{P}}  Vopex_{l,r,t} \times P_{l,r,t} 
+    \end{equation}
+
+.. math::
+    \begin{equation*}
+        \sum_{b \in \mathcal{B}} \Big(\sum_{t \in \mathcal{T}^{sch}} \sum_{l \in \mathcal{L}} Trans^{cost}_{b,r,t} Trans^{in}_{b,l,l',r,t} + \sum_{t \in \mathcal{T}^{sch}} \sum_{l \in \mathcal{L}} Trans^{cost}_{b,r,t}Trans^{out}_{b,l,l',r,t} \Big)
+    \end{equation*}
+
+.. math::
+    \begin{equation*}
+        + \sum_{t \in \mathcal{T}^{sch}} \sum_{r \in \mathcal{R}^{cons}} C_{l,r,t} \times Price_{l,r,t} \Big)
+    \end{equation*}
+
+.. math::
+    \begin{equation}
+        Cap^S_{l,r,t} \leq Cap^{S-max}_{l,r,t} \times X^S_{l,r,t} \hspace{1cm} \forall r \in \mathcal{R}^{storage}, t \in \mathcal{T}^{net}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        Cap^P_{l,p,t} \leq Cap^{P-max}_{l,p,t} \times X^P_{l,p,t}  \hspace{1cm} \forall p \in \mathcal{P}, t \in \mathcal{T}^{net}, l \in \mathcal{L}
+    \end{equation} 
+
+
+.. math::
+    \begin{equation}
+        Trans^{in/out}_{b, l, l', r,t} \leq Trans^{max-in/out}_{b, l,l', r,t} \times X^B_{b,l,l',r,t} \hspace{1cm} \forall r \in \mathcal{R}, t \in \mathcal{T}^{sch} , (l,l') \in \mathcal{L}^2, b \in \mathcal{B} 
+    \end{equation}
+
+
+.. math::
+    \begin{equation}
+        \sum_{p \in \mathcal{P}} P_{l,p,t} \times \eta(p,r) + C_{l,r,t} + \sum_{l' \in \mathcal{L} \cap {l}}Trans^{in}_{l,l',r,t} = \sum_{l' \in \mathcal{L} \cap {l}} Trans^{out}_{l,l',r,t} + Inv_{l,r,t} + S_{l,r,t}  
+    \end{equation}
+
+.. math::
+    \begin{equation*}
+        \forall r \in \mathcal{R}^{cons}, t \in \mathcal{T}^{sch}, l \in \mathcal{L}
+    \end{equation*}
+
+.. math::
+    \begin{equation}
+        S_{l,r,t}, C_{l,r,t}, Inv_{l,r,t}, P_{l,p,t}, Cap^P_{l,p,t}, Cap^S_{l,r,t}, Trans^{in/out}_{b,l,l',r,t} \in R_{\geq 0}
+    \end{equation}
+
+
+The problem can now modeled in energiapy
+
+**import modules**
+
+.. code-block:: python 
+
+    import pandas 
+    from energiapy.components.temporal_scale import Temporal_scale
+    from energiapy.components.resource import Resource, VaryingResource
+    from energiapy.components.process import Process, VaryingProcess
+    from energiapy.components.material import Material
+    from energiapy.components.location import Location
+    from energiapy.components.network import Network
+    from energiapy.components.scenario import Scenario
+    from energiapy.components.transport import Transport
+    from energiapy.components.result import Result 
+    from energiapy.utils.data_utils import make_henry_price_df, remove_outliers
+    from energiapy.model.formulate import formulate, Constraints, Objective
+    from energiapy.utils.nsrdb_utils import fetch_nsrdb_data
+    from energiapy.plot import plot
+    from energiapy.model.solve import solve
+
+**import weather data**
+
+The fetch_nsrdb function accesses the National Solar Radiation Database (NSRDB) Highly Scalable Datasets (HSDS) hosted by NREL on a Amazon Web Services (AWS) cloud through the h5py module To access large datasets, an API key can be requested from NREL. 
+
+1. download data at any latitude longitude (globally) or state-county (because of repetition of county names) pairs within the US.
+
+2. Skim and fetch data which match different specifications,  e.g. wind data for collection point at the highest elevation in the county.
+
+3. While HSDS allows you to splice datasets, the script allows you to also find means within ranges.
+
+4. Arrange data in a dataframe for multiscale analysis, with the temporal indices as tuples.  [Can be saved as .csv/.txt/.json/.pkl]
+
+Here, we import solar data as dni and wind data as wind speed for most populated data point in Harris county (TX) and San Diego county (SD) at an hourly resolution
+
+
+.. code-block:: python 
+
+    weather_sandiego =  fetch_nsrdb_data(attrs = ['wind_speed', 'dni'], year = 2019, state = 'California', county = 'San Diego',\
+            resolution= 'hourly', get = 'min-elevation', save = 'data/sd_solar19')[1] 
+
+    weather_houston =  fetch_nsrdb_data(attrs = ['wind_speed', 'dni'], year = 2019, state = 'Texas', county = 'Harris',\
+            resolution= 'hourly', get = 'min-elevation', save = 'data/ho_solar19')[1] 
+
+**import natural gas prices**
+
+Natural gas prices are from the Henry price index at a daily temporal resolution. 
+
+The energia.make_henry_price_df function implicitly fills in gaps in data such as weekends and public holidays by repeating the last recorded prices
+For e.g. if the 4th of July will use the price for the 3rd of July
+a typical saturday and sunday will take the values for the last friday.
+
+The stretch functionality stretches the values over the hourly temporal scale (8760) from a daily temporal scale (365), again through repetition.  
+
+Moreover, we can remove outliers usig the remove_outliers features in data_utils
+
+.. code-block:: python 
+
+    ng_price = make_henry_price_df(file_name='data/Henry_Hub_Natural_Gas_Spot_Price_Daily.csv', year=2019, stretch=False)
+    ng_price = ng_price.set_index(weather_sandiego.index[::24])
+    ng_price = ng_price.drop(columns= 'scales')
+    ng_price = remove_outliers(ng_price, sd_cuttoff = 3)
+
+**Import demand data**
+
+Here we import the power demand data for San Diego (CAISO for SDGE region) and Houston (ERCOT for COAST region)
+
+.. code-block:: python 
+
+    demand_sandiego = pandas.read_excel('data/HistoricalEMSHourlyLoad-2019.xlsx', index_col= 0)[['SDGE']]
+    demand_houston = pandas.read_excel('data/Native_Load_2019.xlsx')[['COAST']]
+    #Fix Index
+    demand_sandiego = demand_sandiego.set_index(weather_sandiego.index)
+    demand_houston = demand_houston.set_index(weather_houston.index)
+
+
+**Define temporal scale**
+
+The variabilities of energy systems are best captured over a discretized spatio-temporal scale. In energiapy, the first declaration is the temporal scale. 
+
+For e.g.: Here we declare three temporal scales at different levels from right to left. The interpretation of these scales is merely symentic. Scales can be declared as the problem demands.
+- 0, annual, with 1 discretization
+- 1, daily with 365 discretization
+- 2, hourly with 24 discretization
+
+In essence, we are creating a temporal scale of 8760 points.
+
+
+.. code-block:: python 
+
+    scales = Temporal_scale(discretization_list=[1, 365, 24], start_zero= 2019)
+
+**Declare resources**
+
+Resources can be -
+
+*consumed*, e.g. solar, wind
+
+*purchased* (consumed at a cost), e.g. natural gas, water
+
+*sold*, e.g. hydrogen, power
+
+*produced*, e.g. hydrogen, methanol
+
+*stored*, e.g. power as charge or elevated water, hydrogen as cryogenic or compressed
+
+*discharged* (sold for 0 currency), e.g. CO2, O2 (could be assigned profit)
+
+basis can be declared, maximum consumption and storage can be defined
+
+selling and purchase costs can vary. Natural gas and power for example
+
+labels and blocks can be defined
+
+these can be represented as cost factors (0,1) multiplied to a base resource cost
+
+.. code-block:: python
+
+    Solar = Resource(name='Solar', cons_max=100, basis='MW', label='Solar Power')
+
+    Wind = Resource(name='Wind', cons_max= 100, basis='MW', label='Wind Power')
+
+    Power = Resource(name='Power', basis='MW', demand = True, label='Power generated', varying = VaryingResource.deterministic_demand)
+
+    H2 = Resource(name='H2', basis='kg', label='Hydrogen', block='Resource')
+
+    H2O = Resource(name='H2O', cons_max=10**10,
+                price= 0.001, basis='kg', label='Water', block='Resource')
+
+    CH4 = Resource(name='CH4', cons_max=10**10, price=1, basis='kg', label='Natural gas', varying=  VaryingResource.deterministic_price)
+
+    CO2 = Resource(name='CO2', basis='kg', label='Carbon dioxide', block='Resource')
+
+    CO2_Vent = Resource(name='CO2_Vent', sell=True, basis='kg', label='Carbon dioxide - Vented')
+
+    O2 = Resource(name='O2', sell=True, basis='kg', label='Oxygen')
+
+    CO2_DAC = Resource(name='CO2_DAC', basis='kg', label='Carbon dioxide - captured')
+
+
+**Declare processes**
+
+Processes convert a resource into another through the utilization of resources. 
+Essentially, the model is developed as an RTN
+
+.. code-block:: python
+
+    LiI = Process(name='LiI', storage= Power, capex = 1302182, fopex= 41432, vopex = 2000,  prod_max=100, label='Lithium-ion battery', basis = 'MW')
+
+    WF = Process(name='WF', conversion={Wind: -1, Power: 1},capex=990637, fopex=3354, vopex=4953, prod_max=100, label='Wind mill array', varying= VaryingProcess.deterministic_capacity, basis = 'MW')
+
+    PV = Process(name='PV', conversion={Solar: -1, Power: 1}, capex=567000, fopex=872046, vopex=90000, prod_max=100, varying = VaryingProcess.deterministic_capacity, label = 'Solar PV', basis = 'MW')
+
+
+
+    SMRH = Process(name='SMRH', conversion={Power: -1.11*10**(-3), CH4: -3.76, H2O: -23.7, H2: 1, CO2_Vent: 1.03, CO2: 9.332}, capex =2520, fopex = 945, vopex = 0.0515,\
+        prod_max= 10000, label='Steam methane reforming + CCUS')
+
+    SMR = Process(name='SMR', capex = 2400, fopex = 800, vopex = 0.03,  conversion={Power: -1.11*10**(-3), CH4: -3.76, H2O: -23.7, H2: 1, CO2_Vent: 9.4979}, prod_max=10000, label='Steam methane reforming')
+
+    H2FC = Process(name='H2FC', conversion = {H2:-50, Power: 1}, capex =  1.6*10**6, vopex = 3.5, fopex = 0, prod_max = 100, label = 'hydrogen fuel cell')
+
+    DAC = Process(name='DAC', capex = 0.02536, fopex = 0.634, vopex = 0, conversion={Power: -1.93*10**(-4), H2O: -4.048, CO2_DAC: 1}, prod_max=10000, gwp=0, label='Direct air capture')
+
+**Declare locations**
+
+Locations have a set of available processes, the deterministic variability for the:
+
+- price of purchase (consumption) of resource; natural gas in this case.
+  
+- demand data; mileage in this case
+  
+- capacities; here we use dni and windspeed data to capture the intermittent availability of solar and wind power
+
+Note that there is absolutely no limitations on the number of variable resources (demand, purchase price) or processes (capacity) you can consider. 
+
+The scale levels [0,1,2] can be used to declare the resolution at which to handle demand, capacity, and cost.
+
+Note that not all of these are required to build a problem.
+
+.. code-block:: python
+
+    houston = Location(name='HO', processes= {LiI, PV, WF, SMRH, SMR, H2FC, DAC}, demand_factor= {Power: demand_houston}, cost_factor = {CH4: ng_price}, \
+        capacity_factor = {PV: pandas.DataFrame(weather_houston['dni']), WF: pandas.DataFrame(weather_houston['wind_speed'])},\
+            scales=scales, label='Houston', demand_scale_level=2, capacity_scale_level= 2, cost_scale_level= 1)
+
+    sandiego = Location(name='SD', processes= {LiI, PV, WF, H2FC}, demand_factor= {Power: demand_sandiego}, cost_factor = {CH4: ng_price}, \
+        capacity_factor = {PV: pandas.DataFrame(weather_sandiego['dni']), WF: pandas.DataFrame(weather_sandiego['wind_speed'])},\
+            scales=scales, label='SanDiego', demand_scale_level=2, capacity_scale_level= 2, cost_scale_level= 1)
+
+
+**Plotting input data**
+
+energiapy also has significant plotting capabilities. 
+
+The factors for demand, cost, and capacity can be plotted
+
+.. code-block:: python
+
+    plot.capacity_factor(location= sandiego, process= PV, color= 'orange')
+    plot.capacity_factor(location= sandiego, process= WF, color= 'blue')
+    plot.cost_factor (location= sandiego, resource= CH4, color= 'red')
+
+.. image:: multi_loc_pv.png
+
+.. image:: multi_loc_ng.png
+
+
+
+**Declare transport options**
+
+Transport objects translocate resources, and can have associated costs as well as transport losses.
+
+.. code-block:: python
+
+    Train_H2 = Transport(name= 'Train_H2', resources= {H2}, trans_max= 10000, trans_loss= 0.001, trans_cost= 1.667*10**(-3), label= 'Railway for hydrogen transportation')
+    Pipe = Transport(name= 'Pipe', resources= {H2}, trans_max= 10000, trans_loss= 0.001, trans_cost= 0.5*10**(-3), label= 'Railroad transport')
+
+**Declare network**
+
+Networks link locations with transportation. The availability of differnt transport objects and the distances between the locations needs to be provided.
+
+
+.. code-block:: python
+
+    distance_matrix = [
+        [0, 2366],
+        [2366, 0]
+                    ]
+
+    transport_matrix = [
+        [[], [Train_H2, Pipe]],
+        [[Train_H2, Pipe], []] 
+                    ]
+
+    network = Network(name= 'Network', source_locations= [houston, sandiego], sink_locations= [houston, sandiego], distance_matrix= distance_matrix, transport_matrix= transport_matrix) 
+
+**Declare scenario**
+
+The combination of parameter data, locations, and transportation options generates a scenario. 
+
+Scenarios are data sets that can be fed to models for analysis. 
+
+In this case we are generating a scenario for a network with locations Houston and San Diego. The scales need to be consistent.
+
+.. code-block:: python
+
+    scenario = Scenario(name= 'dtw_example', network= network, scales= scales,  expenditure_scale_level= 1, scheduling_scale_level= 2, \
+    network_scale_level= 0, demand_scale_level= 2, label= 'DTW_case')
+
+
+**Formulate milp instance**
+
+Models of different classes can be formulated based on the constraints considered.
+
+In the following case, we optimize the cost while constraining inventory, production, resource balance, transport, and cost
+
+.. code-block:: python
+
+    milp = formulate(scenario= scenario, demand = {sandiego: {Power: 30}, houston: {Power: 20}}, \
+    constraints={Constraints.cost, Constraints.inventory, Constraints.production, Constraints.resource_balance, Constraints.transport}, objective= Objective.cost)
+
+**Solve the instance**
+
+The instance can then be solved using an appropriate solver. Here we solve the problem using the Gurobi solver.
+
+.. code-block:: python
+
+    results = solve(scenario = scenario, instance= milp, solver= 'gurobi', name=f"Multi-Loc", print_solversteps = True)
+
+
+**Plotting output**
+
+The results can be analyzed, and used for illustrations.
+Note that plotting of results requires the provision of the names as opposed to energiapy objects.
+
+.. code-block:: python
+
+    plot.schedule(results= results, y_axis= 'S', component= 'Power', location= 'SD')
+
+.. image:: multi_loc_sch.png
+
+
+
+
+
diff --git a/docs/example_small.rst b/docs/example_small.rst
new file mode 100644
index 00000000..4aa8c944
--- /dev/null
+++ b/docs/example_small.rst
@@ -0,0 +1,308 @@
+Small Energy Systems MILP Example
+=================================
+
+Consider a simple system with three processes
+
+- Solar PV with varying capacity
+- Wind Farm with varying capacity
+- Lithium-ion battery storage
+
+and varying demand for power.
+
+The objective is to meet demand over four seasons by generating power using the PV and Wind Farm, 
+storing the power in a Lithium-ion battery if necessary and meeting a varying demand for Power
+
+The problem can be modeled over two scales
+
+- 0, network scale with 1 time period where the network decisions such as setting up a unit are taken
+- 1, scheduling and demand scales with 4 time periods, where the resource flows are determined
+
+The problem is modeled as shown below
+
+**Nomenclature**
+
+The sets and variables used are stated here
+
+*Sets*
+
+
+- R - set of all resources r
+- P - set of all processes p
+- T - set of temporal periods p
+
+
+*Subsets*
+
+- R\ :sup:`storage` - set of resources that can be stored
+- R\ :sup:`sell` - set of resources that can be discharged
+- R\ :sup:`demand` - set of resources that meet  demand
+- R\ :sup:`cons` - set of resources that can be consumed
+- P\ :sup:`uncertain` - set of processes with uncertain capacity
+- T - set of temporal periods 
+- T\ :sup:`net` - set of temporal periods t for network level decision making
+- T\ :sup:`sch` - set of temporal periods t for schedule level decision making
+
+
+*Continuous Variables*
+
+
+- P\ :sub:`p,t` - production level of p :math:`{\in}`  P in time period t :math:`{\in}` T\ :sup:`sch`  
+    
+- C\ :sub:`r,t` - consumption of r :math:`{\in}` R\ :sup:`cons` time period t :math:`{\in}` T\ :sup:`sch` 
+    
+- S\ :sub:`r,t` - discharge of r :math:`{\in}` R\ :sup:`demand` time period t :math:`{\in}` T\ :sup:`sch` 
+    
+- Inv\ :sub:`r,t` - inventory level of r :math:`{\in}` R\ :sup:`storage`  in time period t :math:`{\in}` T\ :sup:`sch`
+    
+- Cap\ :sup:`S` \ :sub:`r,t` - installed inventory capacity for resource r :math:`{\in}`  R\ :sup:`storage` in time period t :math:`{\in}` T\ :sup:`net`
+    
+- Cap\ :sup:`P` \ :sub:`p,t` - installed production capacity for process p :math:`{\in}` P in time period t :math:`{\in}` T\ :sup:`net`
+    
+
+
+
+*Binary Variables*
+
+
+- X\ :sup:`P` \ :sub:`p,t` - network binary for production process p :math:`{\in}` P in time period t :math:`{\in}` T\ :sup:`net`
+- X\ :sup:`S` \ :sub:`r,t` - network binary for inventory of resource r :math:`{\in}` R\ :sup:`storage` in time period t :math:`{\in}` T\ :sup:`net`
+
+*Parameters*
+
+
+- Cap\ :sup:`P-max` \ :sub:`p,t` - maximum production capacity of process p :math:`{\in}` P in time period t :math:`{\in}` T\ :sup:`net`
+- Cap\ :sup:`S-max` \ :sub:`r,t` - maximum inventory capacity for process r :math:`{\in}` R\ :sup:`storage` in time period t :math:`{\in}` T\ :sup:`net`
+- Capex\ :sub:`p,t` - capital expenditure for process p :math:`{\in}` P in time t :math:`{\in}` T\ :sup:`net`
+- Price\ :sub:`r,t` - purchase price for resource r :math:`{\in}` R\ :sup:`cons` in time t :math:`{\in}` T\ :sup:`sch`
+- C\ :sup:`max` \ :sub:`r,t` - maximum consumption availability for resource r :math:`{\in}` R\ :sup:`cons` in time t :math:`{\in}` T\ :sup:`sch`
+- D\ :sub:`r,t` - demand for resource r in R\ :sup:`sell` in time t :math:`{\in}` T\ :sup:`sch`
+
+**MILP Formulation**
+
+Given is a general MILP modeling and optimization framework for simultaneous network design and scheduling for a single location problem.
+
+.. math::
+    \begin{equation}
+        min \sum_{t \in \mathcal{T}^{net}} \sum_{p \in \mathcal{P}} Capex_{p,t} \times Cap^P_{p,t} + \sum_{t \in \mathcal{T}^{sch}} \sum_{r \in \mathcal{R}^{cons}}  Price_{r,t}  \times C_{r,t} + \sum_{t \in \mathcal{T}^{sch}} \sum_{p \in \mathcal{P}}  Vopex_{r,t} \times P_{r,t} 
+    \end{equation}
+
+
+.. math::
+    \begin{equation}
+        Cap^S_{r,t} \leq Cap^{S-max}_{r,t} \times X^S_{r,t} \hspace{1cm} \forall r \in \mathcal{R}^{storage}, t \in \mathcal{T}^{net}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        Cap^P_{p,t} \leq Cap^{P-max}_{p,t} \times X^P_p  \hspace{1cm} \forall p \in \mathcal{P}, t \in \mathcal{T}^{net}
+    \end{equation} 
+
+.. math::
+    \begin{equation}
+        P_{p,t} \leq Cap^{P}_{p,t}  \hspace{1cm} \forall p \in \mathcal{P}, t \in \mathcal{T}^{sch}
+    \end{equation} 
+
+.. math::
+    \begin{equation}
+        Inv_{r,t} \leq Cap^{S}_{r,t}  \hspace{1cm} \forall r \in \mathcal{R}^{storage}, t \in \mathcal{T}^{sch}
+    \end{equation} 
+
+
+.. math::
+    \begin{equation}
+        - S_{r,t} \leq - D_{r,t}  \hspace{1cm} \forall r \in \mathcal{R}, t \in \mathcal{T}^{sch}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        C_{r,t} \leq C^{max}_{r,t} \hspace{1cm} \forall r \in \mathcal{R}, t \in \mathcal{T}^{sch}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        - S_{r,t} + \sum_{p \in \mathcal{P}} P_{p,t} \times \eta(p,r) = 0 \hspace{1cm} \forall r \in \mathcal{R}^{sell}, t \in \mathcal{T}^{sch}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        -Inv_{r,t} + \sum_{p \in \mathcal{P}} P_{p,t} \times \eta(p,r) = 0 \hspace{1cm} \forall r \in \mathcal{R}^{stored}, t \in \mathcal{T}^{sch}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        \sum_{p \in \mathcal{P}} P_{p,t} \times \eta(p,r) + C_{r,t} = 0 \hspace{1cm} \forall r \in \mathcal{R}^{cons}, t \in \mathcal{T}^{sch}
+    \end{equation}
+
+.. math::
+    \begin{equation}
+        S_{r,t}, C_{r,t}, Inv_{r,t}, P_{p,t}, Cap^P_p, Cap^S_r \in R_{\geq 0}
+    \end{equation}
+
+
+**Import modules**
+
+.. code-block:: python
+
+    import pandas 
+    from energiapy.components.temporal_scale import Temporal_scale
+    from energiapy.components.resource import Resource, VaryingResource
+    from energiapy.components.process import Process, ProcessMode, VaryingProcess
+    from energiapy.components.location import Location
+    from energiapy.components.scenario import Scenario
+    from energiapy.components.result import Result 
+    from energiapy.model.formulate import formulate, Constraints, Objective
+    from energiapy.plot import plot
+    from energiapy.model.solve import solve
+
+**Declare temporal scale**
+
+Consider four seasons in a year.
+
+Network decisions are taken annually (scale level 0)
+
+Scheduling decisions are taken seasonally (scale level 1)
+
+.. code-block:: python
+    
+    scales = Temporal_scale(discretization_list= [1, 4])
+
+**Declare resources**
+
+Resources can be declared with attributes such as maximum consumption (cons_max), resource price (price), maximum allowed inventory (store_max)
+
+As also whether they can be discharged (sell), have to meet demand (demand)
+
+.. code-block:: python
+
+    Solar = Resource(name='Solar', cons_max=100, basis='MW', label='Solar Power')
+
+    Wind = Resource(name='Wind', cons_max= 100, basis='MW', label='Wind Power')
+
+    Power = Resource(name='Power', basis='MW', demand = True, label='Power generated', varying = VaryingResource.deterministic_demand)
+
+**Declare processes**
+
+Processes consume resources and can be of three type:
+
+- storage, if storage = some_resource 
+- single mode, as with the processes defined here wherein a conversions are provided
+- multi mode, if a multiconversion dict is provided
+
+.. code-block:: python
+
+    LiI = Process(name='LiI', storage= Power, capex = 1302182, fopex= 41432, vopex = 2000,  prod_max=100, label='Lithium-ion battery', basis = 'MW')
+
+    WF = Process(name='WF', conversion={Wind: -1, Power: 1},capex=990637, fopex=3354, vopex=4953, prod_max=100, label='Wind mill array', varying= VaryingProcess.deterministic_capacity, basis = 'MW')
+
+    PV = Process(name='PV', conversion={Solar: -1, Power: 1}, capex=567000, fopex=872046, vopex=90000, prod_max=100, varying = VaryingProcess.deterministic_capacity, label = 'Solar PV', basis = 'MW')
+
+
+**Location level input data**
+
+Factors are normalized, and can be used to account for:
+
+- variable resource demand (demand_factor)
+- intermittent resource availability (capacity factor)
+- varying resource purchase cost (cost factor)
+
+.. code-block:: python
+
+    demand_factor = pandas.DataFrame(data={'Power': [0.6, 1, 0.8, 0.3]})
+    capacity_factor_pv = pandas.DataFrame(data={'PV': [0.6, 0.8, 0.9, 0.7]})
+    capacity_factor_wf = pandas.DataFrame(data={'WF': [0.9, 0.8, 0.5, 0.7]})
+
+**Declare location**
+
+Locations are essentially a set of processes, the required resources are collected implicitly.
+
+Location-wise capacity, demand, and cost factors can be provided. 
+
+The scales of the capacity and demand data need to be provided as well.
+
+.. code-block:: python
+
+    place = Location(name='place', processes= {LiI, PV, WF}, demand_factor = {Power: demand_factor}, capacity_factor= {PV: capacity_factor_pv, WF:capacity_factor_wf}, capacity_scale_level= 1, demand_scale_level = 1, scales=scales, label='some place')
+
+*plot varying factors*
+
+Plotting functions in energiapy.plot can be used to plot the factors
+
+.. code-block:: python
+
+    plot.capacity_factor(location= place, process= PV, fig_size= (9,5), color= 'orange')
+    plot.demand_factor(location= place, resource= Power, fig_size= (9,5), color= 'red')
+
+.. image:: capacity_factor_pv.png 
+
+.. image:: demand_factor_pw.png 
+
+
+**Declare scenario**
+
+The combination of parameter data, locations, and transportation options generates a scenario. 
+
+Scenarios are data sets that can be fed to models for analysis. 
+
+In this case we are generating a scenario for the location houston. The scales need to be consistent.
+
+The demand, network, scheduling, and expenditure scales need to be provided. They all default to 0.
+
+.. code-block:: python
+
+    case = Scenario(name= 'case', network= place, network_scale_level= 0, demand_scale_level = 1, scheduling_scale_level= 1, scales= scales, label= 'small scenario')
+
+**Formulate MILP**
+
+Models can be formulated using different constraints and objectives.
+
+milp is a pyomo instance, additional constraints can be provided in a bespoke manner
+
+.. code-block:: python
+
+    milp = formulate(scenario= case, demand = {place: {Power: 200}}, constraints={Constraints.cost, Constraints.inventory, Constraints.production, Constraints.resource_balance}, \
+        objective= Objective.cost)
+
+**Solve**
+
+To solve the model, the solve requires a scenario and a modeling instance to be provided. 
+
+Also a solver needs to be chosen.
+
+.. code-block:: python
+
+    results = solve(scenario = case, instance= milp, solver= 'gurobi', name=f"MILP")
+
+**Results**
+
+Models can be summarized as shown below:
+
+.. code-block:: python
+
+    results.model_summary()
+
+*Plot results*
+
+Some handy plotting functions such as schedule can plot the production, consumption, sales, inventory schedules. 
+
+In the example below, the production schedule for the windfarm (WF), and the inventory levels for stored power is shown. 
+
+.. code-block:: python
+
+    plot.schedule(results= results, y_axis= 'P', component= 'WF', location = 'place', fig_size= (9,5), color = 'blue')
+    plot.schedule(results= results, y_axis= 'Inv', component= 'LiI_Power_stored', location = 'place', fig_size= (9,5), color = 'green')
+
+.. image:: sch_wf.png 
+
+.. image:: sch_pow.png 
+
+
+All inputs are stored in results.component
+
+All outputs are stored in results.output
+
+Values can be accessed as shown below
+
+.. code-block:: python
+
+    results.output['X_P']
+    results.output['Cap_P']
\ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
index af487ba6..0e457edf 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,26 +1,34 @@
-.. energiapy documentation master file, created by
-   sphinx-quickstart on Wed Feb 15 12:55:59 2023.
+.. energiapy documentation master file, created by Rahul Kakodkar
+   sphinx-quickstart on Wed Mar 15 15:20:31 2023.
    You can adapt this file completely to your liking, but it should at least
    contain the root `toctree` directive.
 
+.. mdinclude:: ../README.md
+
 Welcome to energiapy's documentation!
 =====================================
 
-.. mdinclude:: ../README.md
-
+Tutorial
+--------
 
 .. toctree::
    :maxdepth: 2
-   :caption: Examples
+   :caption: Overview:
+
+   example_small
+   example_multiloc
+   example_mpLP
+   example_ahc
 
-   small_example
 
+API
+---
 
 .. toctree::
-   :maxdepth: 5
-   :caption: API
+    :maxdepth: 5
+    :caption: API
 
-   energiapy
+    energiapy
 
 Indices and tables
 ==================
@@ -28,3 +36,4 @@ Indices and tables
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
+
diff --git a/docs/make.bat b/docs/make.bat
deleted file mode 100644
index 954237b9..00000000
--- a/docs/make.bat
+++ /dev/null
@@ -1,35 +0,0 @@
-@ECHO OFF
-
-pushd %~dp0
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
-	set SPHINXBUILD=sphinx-build
-)
-set SOURCEDIR=.
-set BUILDDIR=_build
-
-%SPHINXBUILD% >NUL 2>NUL
-if errorlevel 9009 (
-	echo.
-	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
-	echo.installed, then set the SPHINXBUILD environment variable to point
-	echo.to the full path of the 'sphinx-build' executable. Alternatively you
-	echo.may add the Sphinx directory to PATH.
-	echo.
-	echo.If you don't have Sphinx installed, grab it from
-	echo.https://www.sphinx-doc.org/
-	exit /b 1
-)
-
-if "%1" == "" goto help
-
-%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
-goto end
-
-:help
-%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
-
-:end
-popd
diff --git a/docs/mars.png b/docs/mars.png
new file mode 100644
index 00000000..ad691982
Binary files /dev/null and b/docs/mars.png differ
diff --git a/docs/multi_loc_dem.png b/docs/multi_loc_dem.png
new file mode 100644
index 00000000..ed03185f
Binary files /dev/null and b/docs/multi_loc_dem.png differ
diff --git a/docs/multi_loc_ng.png b/docs/multi_loc_ng.png
new file mode 100644
index 00000000..e93b000e
Binary files /dev/null and b/docs/multi_loc_ng.png differ
diff --git a/docs/multi_loc_pv.png b/docs/multi_loc_pv.png
new file mode 100644
index 00000000..613cd2bd
Binary files /dev/null and b/docs/multi_loc_pv.png differ
diff --git a/docs/multi_loc_sch.png b/docs/multi_loc_sch.png
new file mode 100644
index 00000000..6d9cb90c
Binary files /dev/null and b/docs/multi_loc_sch.png differ
diff --git a/docs/multi_loc_wf.png b/docs/multi_loc_wf.png
new file mode 100644
index 00000000..bbefa3d6
Binary files /dev/null and b/docs/multi_loc_wf.png differ
diff --git a/docs/multiloc_im.png b/docs/multiloc_im.png
new file mode 100644
index 00000000..574c1b59
Binary files /dev/null and b/docs/multiloc_im.png differ
diff --git a/docs/sch_full_wf.png b/docs/sch_full_wf.png
new file mode 100644
index 00000000..6c12fc74
Binary files /dev/null and b/docs/sch_full_wf.png differ
diff --git a/docs/sch_red_wf.png b/docs/sch_red_wf.png
new file mode 100644
index 00000000..5e6d11b8
Binary files /dev/null and b/docs/sch_red_wf.png differ
diff --git a/docs/setup.rst b/docs/setup.rst
new file mode 100644
index 00000000..552eb49d
--- /dev/null
+++ b/docs/setup.rst
@@ -0,0 +1,7 @@
+setup module
+============
+
+.. automodule:: setup
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/small_example.rst b/docs/small_example.rst
deleted file mode 100644
index 5d8e4f9a..00000000
--- a/docs/small_example.rst
+++ /dev/null
@@ -1,182 +0,0 @@
-Small Energy Systems MILP Example
-=================================
-
-A simple problem with three processes
-
-- Solar PV with varying capacity factor
-- Wind Farm with varying capacity factor
-- Lithium-ion battery storage
-
-and varying demand.
-
-The problem is modeled over two scales
-
-- 0, network scale with 1 time period
-- 1, scheduling and demand scales with 4 time periods
-
-**Import modules**
-
-.. code-block:: python
-
-    import pandas 
-    from energiapy.components.temporal_scale import Temporal_scale
-    from energiapy.components.resource import Resource
-    from energiapy.components.process import Process, ProcessMode
-    from energiapy.components.location import Location
-    from energiapy.components.scenario import Scenario
-    from energiapy.components.result import Result 
-    from energiapy.model.formulate import formulate, Constraints, Objective
-    from energiapy.plot import plot
-    from energiapy.model.solve import solve
-
-**Input Data**
-
-Factors are normalized, and can be used to account for:
-
-- variable resource demand (demand_factor)
-- intermittent resource availability (capacity factor)
-- varying resource purchase cost (cost factor)
-
-.. code-block:: python
-
-    demand_factor = pandas.DataFrame(data={'Power': [0.6, 1, 0.8, 0.3]})
-    capacity_factor_pv = pandas.DataFrame(data={'PV': [0.6, 0.8, 0.9, 0.7]})
-    capacity_factor_wf = pandas.DataFrame(data={'WF': [0.9, 0.8, 0.5, 0.7]})
-
-**Declare temporal scale**
-
-Consider four seasons in a year.
-
-Network decisions are taken annually (scale level 0)
-
-Scheduling decisions are taken seasonally (scale level 1)
-
-.. code-block:: python
-    
-    scales = Temporal_scale(discretization_list= [1, 4])
-
-**Declare resources**
-
-Resources can be declared with attributes such as maximum consumption (cons_max), resource price (price), maximum allowed inventory (store_max)
-
-As also whether they can be discharged (sell), have to meet demand (demand)
-
-.. code-block:: python
-
-    Solar = Resource(name='Solar', cons_max=100, basis='MW', label='Solar Power')
-
-    Wind = Resource(name='Wind', cons_max= 100, basis='MW', label='Wind Power')
-
-    Power = Resource(name='Power', basis='MW', demand = True, block = 'bla', label='Power generated', varying = True)
-
-**Declare processes**
-
-Processes consume resources and can be of three type:
-
-- storage, if storage = some_resource 
-- single mode, as with the processes defined here wherein a conversions are provided
-- multi mode, if a multiconversion dict is provided
-
-.. code-block:: python
-
-    LiI = Process(name='LiI', storage= Power, capex = 1302182, fopex= 41432, vopex = 2000,  prod_max=100, label='Lithium-ion battery', basis = 'MW')
-
-    WF = Process(name='WF', conversion={Wind: -1, Power: 1},capex=990637, fopex=3354, vopex=4953, prod_max=100, label='Wind mill array', varying= True, basis = 'MW')
-
-    PV = Process(name='PV', conversion={Solar: -1, Power: 1}, capex=567000, fopex=872046, vopex=90000, prod_max=100, varying = True, label = 'Solar PV', basis = 'MW')
-
-**Declare location**
-
-Locations are essentially a set of processes, the required resources are collected implicitly.
-
-Location-wise capacity, demand, and cost factors can be provided. 
-
-The scales of the capacity and demand data need to be provided as well.
-
-.. code-block:: python
-
-    place = Location(name='place', processes= {LiI, PV, WF}, demand_factor = {Power: demand_factor}, capacity_factor= {PV: capacity_factor_pv, WF:capacity_factor_wf}, capacity_scale_level= 1, demand_scale_level = 1, scales=scales, label='some place')
-
-*plot varying factors*
-
-Plotting functions in energiapy.plot can be used to plot the factors
-
-.. code-block:: python
-
-    plot.capacity_factor(location= place, process= PV, fig_size= (9,5), color= 'orange')
-    plot.demand_factor(location= place, resource= Power, fig_size= (9,5), color= 'red')
-
-.. image:: capacity_factor_pv.png 
-
-.. image:: demand_factor_pw.png 
-
-
-**Declare scenario**
-
-The combination of parameter data, locations, and transportation options generates a scenario. 
-
-Scenarios are data sets that can be fed to models for analysis. 
-
-In this case we are generating a scenario for the location houston. The scales need to be consistent.
-
-The demand, network, scheduling, and expenditure scales need to be provided. They all default to 0.
-
-.. code-block:: python
-
-    case = Scenario(name= 'case', network= place, network_scale_level= 0, demand_scale_level = 1, scheduling_scale_level= 1, scales= scales, label= 'small scenario')
-
-**Formulate MILP**
-
-Models can be formulated using different constraints and objectives.
-
-milp is a pyomo instance, additional constraints can be provided in a bespoke manner
-
-.. code-block:: python
-
-    milp = formulate(scenario= case, demand = 200, constraints={Constraints.cost, Constraints.inventory, Constraints.production, Constraints.resource_balance}, \
-        objective= Objective.cost)
-
-**Solve**
-
-To solve the model, the solve requires a scenario and a modeling instance to be provided. 
-
-Also a solver needs to be chosen.
-
-.. code-block:: python
-
-    results = solve(scenario = case, instance= milp, solver= 'gurobi', name=f"MILP")
-
-**Results**
-
-Models can be summarized as shown below:
-
-.. code-block:: python
-
-    results.model_summary()
-
-*Plot results*
-
-Some handy plotting functions such as schedule can plot the production, consumption, sales, inventory schedules. 
-
-In the example below, the production schedule for the windfarm (WF), and the inventory levels for stored power is shown. 
-
-.. code-block:: python
-
-    plot.schedule(results= results, y_axis= 'P', component= 'WF', location = 'place', fig_size= (9,5), color = 'blue')
-    plot.schedule(results= results, y_axis= 'Inv', component= 'LiI_Power_stored', location = 'place', fig_size= (9,5), color = 'green')
-
-.. image:: sch_wf.png 
-
-.. image:: sch_pow.png 
-
-
-All inputs are stored in results.component
-
-All outputs are stored in results.output
-
-Values can be accessed as shown below
-
-.. code-block:: python
-
-    results.output['X_P']
-    results.output['Cap_P']
\ No newline at end of file
diff --git a/index.rst b/index.rst
new file mode 100644
index 00000000..1811f292
--- /dev/null
+++ b/index.rst
@@ -0,0 +1,38 @@
+.. energiapy documentation master file, created by
+   sphinx-quickstart on Wed Mar 15 16:51:49 2023.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+
+.. mdinclude:: ../README.md
+
+Welcome to energiapy's documentation!
+=====================================
+
+Tutorial
+--------
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Overview:
+
+   example_small
+   example_multiloc
+   example_mpLP
+   example_ahc
+
+API
+---
+
+.. toctree::
+    :maxdepth: 5
+    :caption: API
+
+    energiapy
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/requirements.txt b/requirements.txt
index 8dc19586..206ee5ef 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,11 +1,9 @@
 numpy
 pandas
-Pyomo
+pyomo
 gurobipy
 h5pyd
 matplotlib
-setuptools
-pvlib
 scikit-learn
 openpyxl
 ppopt
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 93faa9fe..281c0322 100644
--- a/setup.py
+++ b/setup.py
@@ -1,39 +1,43 @@
-# %%
 from setuptools import setup, find_packages
-
-__version__ = "1.0.0"
+import os
+__version__ = "1.0.4"
 
 short_desc = (
     "Python-based energy systems modeling and optimization tool"
 )
 
-with open('README.md') as f:
-    long_description = f.read()
-
-setup(
-    name='energiapy',
-    version=__version__,
-    author='Rahul Kakodkar',
-    author_email='cacodcar@tamu.edu',
-    description=short_desc,
-    long_description=long_description,
-    long_description_content_type="text/markdown",
-    license='',
-    url='',
-    install_requires=[
-        'numpy',
-        'pandas',
-        'Pyomo',
-        'gurobipy',
-        'h5pyd',
-        'scipy',
-        'matplotlib',
-        'setuptools',
-        'pvlib',
-        'scikit-learn'
-    ],
-    packages=find_packages(where='src'),
-    package_dir={'': 'src'}
-)
-
-# %%
+def setup_package():
+    with open('README.md', encoding='utf-8') as f:
+            long_description = f.read()
+        
+    setup(
+        name='energiapy',
+        version=__version__,
+        author='Rahul Kakodkar',
+        author_email='cacodcar@tamu.edu',
+        description=short_desc,
+        long_description=long_description,
+        long_description_content_type="text/markdown",
+        license='MIT',
+        url='https://github.com/TAMUparametric/energiapy',
+        install_requires=[
+            'numpy',
+            'pandas',
+            'pyomo',
+            'h5pyd',
+            'scipy',
+            'matplotlib',
+            'scikit-learn',
+            'openpyxl',
+            'ppopt',
+            'gurobipy',
+        ],
+        extras_require={
+            'test': ['pytest'],
+        },
+        packages=find_packages(where='src'),
+        package_dir={'': 'src'},
+    )
+    
+if __name__ == '__main__':
+    setup_package()
\ No newline at end of file
diff --git a/src/__init__.py b/src/__init__.py
index d66ee6eb..e69de29b 100644
--- a/src/__init__.py
+++ b/src/__init__.py
@@ -1,11 +0,0 @@
-"""main init file for energiapy package
-"""
-
-__author__ = "Rahul Kakodkar"
-__copyright__ = "Copyright 2022, Multi-parametric Optimization & Control Lab"
-__credits__ = ["Rahul Kakodkar", "Efstratios N. Pistikopoulos"]
-__license__ = "Open"
-__version__ = "0.0.1"
-__maintainer__ = "Rahul Kakodkar"
-__email__ = "cacodcar@tamu.edu"
-__status__ = "Production"
diff --git a/src/energiapy/aggregation/__init__.py b/src/energiapy/aggregation/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/energiapy/aggregation/ahc.py b/src/energiapy/aggregation/ahc.py
new file mode 100644
index 00000000..7408efe9
--- /dev/null
+++ b/src/energiapy/aggregation/ahc.py
@@ -0,0 +1,249 @@
+"""Clustering with Agglomerative Hierarchial Clustering (AHC)
+"""
+
+__author__ = "Rahul Kakodkar"
+__copyright__ = "Copyright 2023, Multi-parametric Optimization & Control Lab"
+__credits__ = ["Rahul Kakodkar", "Efstratios N. Pistikopoulos"]
+__license__ = "Open"
+__version__ = "1.0.4"
+__maintainer__ = "Rahul Kakodkar"
+__email__ = "cacodcar@tamu.edu"
+__status__ = "Production"
+
+import pandas
+import numpy
+# from pyearth import Earth
+from itertools import product
+from typing import Union, Tuple
+from sklearn.cluster import AgglomerativeClustering
+from sklearn.neighbors import NearestCentroid
+from ..utils.math_utils import scaler, generate_connectivity_matrix, find_euclidean_distance
+from ..components.scenario import Scenario
+from ..components.resource import Resource
+from ..components.process import Process
+from ..components.temporal_scale import Temporal_scale
+from ..components.network import Network
+from ..components.location import Location
+from enum import Enum, auto
+import matplotlib.pyplot as plt
+
+
+class IncludeAHC(Enum):
+    cost = auto()
+    demand = auto()
+    capacity = auto()
+    
+class Fit(Enum):
+    poly2 = auto()
+    poly3 = auto()
+    # mars = auto()
+
+    
+
+def agg_hierarchial(scales: Temporal_scale, scale_level: int, periods: int, include:list, cost_factor: dict = None, \
+    capacity_factor: dict = None, demand_factor: dict = None):
+    """perform agglomerative hierarchial clustering over time-series data
+
+    Args:
+        scales (Temporal_scale): scales of the problme
+        scale_level (int): scale level to cluster at
+        periods (int): number of clustering periods
+        cost_factor (dict, optional): factor to account for varying cost factors. Defaults to None.
+        capacity_factor (dict, optional): factor to account for varying production capacity. Defaults to None.
+        demand_factor (dict, optional): factor to account for varying resource demand. Defaults to None.
+
+
+    Returns:
+        _type_: _description_
+    """
+    
+    if IncludeAHC.cost in include:
+        # cost_factor_df = pandas.DataFrame(cost_factor)
+        cost_factor_df = pandas.concat([pandas.DataFrame(cost_factor[i]) for i in cost_factor.keys()], axis = 1)
+    else:
+        cost_factor_df = None
+    
+    if IncludeAHC.capacity in include:    
+        # capacity_factor_df = pandas.DataFrame(capacity_factor)
+        capacity_factor_df = pandas.concat([pandas.DataFrame(capacity_factor[i]) for i in capacity_factor.keys()], axis = 1)
+        
+    else:
+        capacity_factor_df = None
+    
+    if IncludeAHC.demand in include:
+        demand_factor_df = pandas.concat([pandas.DataFrame(demand_factor[i]) for i in demand_factor.keys()], axis = 1)
+        # demand_factor_df = pandas.DataFrame(demand_factor)
+    else:
+        demand_factor_df = None
+    
+ 
+    
+    df = pandas.concat([cost_factor_df, capacity_factor_df, demand_factor_df],
+                       axis=1).reset_index(drop=True) # makes a common data frame with all different data sets
+    df.columns = numpy.arange(len(df.columns))
+    parent_scale = scales.scale[scale_level-1]
+    scale = scales.scale[scale_level] # the scale for which to cluster, e.g.: day
+    
+    child_scale = scales.scale[scale_level+1] # the lower scale which is nested under the parent scale, e.g. : hour
+        
+    split_df = numpy.array_split(df, len(parent_scale))
+    # output_df = pandas.DataFrame()
+    # rep_dict_iter = {i: 0 for i in range(len(parent_scale))} 
+    
+    rep_dict_iter = {}    
+       
+    
+    #creates a new Temporal_scale object on the reduced scale
+    reduced_dicretization_list = list(scales.discretization_list)
+    reduced_dicretization_list[scale_level] = periods
+    reduced_temporal_scale = Temporal_scale(reduced_dicretization_list)
+
+    reduced_scenario_scaleiter = numpy.array_split(reduced_temporal_scale.scale_iter(scale_level= scale_level +1), len(parent_scale))
+    for parent_iter in range(len(parent_scale)):
+        
+        if len(df) == len(parent_scale)*len(scale):
+            scaled_df = scaler(input_df=split_df[parent_iter], scale = scale)
+        else:
+            scaled_df = scaler(input_df=split_df[parent_iter], scale = scale,
+                        child_scale=child_scale) # reshapes the data frame, e.g. instead of 8760 linear data points, creates a 365x24 matrix
+        
+        scaled_array = scaled_df.to_numpy() # makes an array instead of a dataframe, as raw data is better handled in other libraries, removes dependencies 
+
+        connectivity_matrix = generate_connectivity_matrix(scale_len= len(scale)) # make a matrix to ensure that chronology is maintained 
+        ahc = AgglomerativeClustering(n_clusters=periods, metric='euclidean', connectivity=connectivity_matrix,
+                                    linkage='ward', compute_full_tree=True)
+        clustered_array = ahc.fit_predict(scaled_array)
+        cluster_labels = ahc.labels_  # get list of representative days
+
+        nearest_centroid_array = NearestCentroid().fit(
+            scaled_array, clustered_array).centroids_  # get the centroids of the clusters
+        nearest_centroid_df = pandas.DataFrame(
+            data=nearest_centroid_array, columns=[col for col in scaled_df])
+        # add cluster numbers to the dataset
+        scaled_df['cluster_no'] = cluster_labels
+        nearest_centroid_df['cluster_no'] = scaled_df['cluster_no'].unique()
+
+        euclidean_distance_list = []
+        closest_to_centroid_df = pandas.DataFrame(
+            columns=['period', 'rep_period', 'cluster_wt'])
+
+        for cluster_no in nearest_centroid_df['cluster_no']:
+            # -1 skips cluster_no row
+            cluster_array = scaled_df[scaled_df['cluster_no']
+                                    == cluster_no].iloc[:, :-1].to_numpy()
+            centroid_array = nearest_centroid_df[nearest_centroid_df['cluster_no']
+                                                == cluster_no].iloc[:, :-1].to_numpy()
+            centroid = centroid_array.tolist()[0]
+            for cluster in cluster_array: # finding euclidean distance from each data point to centroid 
+                cluster_point = [cluster for cluster in cluster]
+                euclidean_distance = find_euclidean_distance(
+                    cluster_point, centroid)
+                euclidean_distance_list.append(euclidean_distance)
+
+        scaled_df['ED'] = euclidean_distance_list #find the actual day closest to centroid
+        closest_to_centroid_df['period'] = (
+            scaled_df['cluster_no'].value_counts().index.values)
+        cluster_wts = [i for i in scaled_df['cluster_no'].value_counts()]
+        closest_to_centroid_df['cluster_wt'] = cluster_wts
+
+        list_ = []
+        for i in closest_to_centroid_df['period']:
+            list_.append(
+                scaled_df.index.values[scaled_df['ED'] == scaled_df['ED'][scaled_df['cluster_no'] == i].min()][0])#find the actual day closest to centroid
+        closest_to_centroid_df['rep_period'] = list_
+        closest_to_centroid_df = closest_to_centroid_df.sort_values(
+            by=['rep_period']).reset_index(drop=True)
+        closest_to_centroid_df['period'] = list(
+            range(len(closest_to_centroid_df['period'])))
+
+        
+        current_iter = [ tuple(i) for i in reduced_scenario_scaleiter[parent_iter]]
+   
+        output_dict= {scale: {'rep_period': (parent_iter, \
+            closest_to_centroid_df['rep_period'][closest_to_centroid_df['period'] == scale[scale_level]].values[0], *scale[scale_level+1:]),
+                            'cluster_wt': closest_to_centroid_df['cluster_wt'][closest_to_centroid_df['period'] == scale[scale_level]].values[0]}\
+                                for scale in current_iter}
+
+        rep_dict_iter = {**rep_dict_iter, **output_dict} #V. important: clusters each index in the parent scale individually and combines dict
+    # rep_dict = {rep_dict_iter[i].values() for i}
+
+    # puts all relevant data in a dictionary
+
+    
+    wcss_sum = sum(i for i in scaled_df['ED'])/periods # collects the error data
+
+    numpy.info_dict = {
+        'wcss_sum': wcss_sum
+    }
+    
+    return rep_dict_iter, reduced_temporal_scale, numpy.info_dict
+
+# TODO - call methods as a function
+# TODO  - Handle multiple outputs
+
+
+def agg_hierarchial_elbow(scenario: Scenario, scale_level: int, include: list, range_list: list = None, fit: Fit = None) -> Tuple[list, int]:
+    """calculate the error of a particular clustering method over a range of different cluster periods
+
+    Args:
+        scales (Temporal_scale): scales of the problem
+        scale_level (int): scale over which to cluster
+        include (list): list of factors to include
+        range_list (list, optional): range of clustering days over which to compute error. Defaults to None.
+        fit (Fit, optional): a polyfit curve of 2nd or 3rd degree, or MARS fit to be shown in the results plot. Defaults to Fit.mars.
+    
+    Returns:
+        Tuple(list, int): error for each cluster period returned as a list
+    """
+    
+    if range_list is None:
+        iter_ = scenario.scales.scale[scale_level]
+    else:
+        iter_ = range_list
+    wcss_list  = []
+    for i in iter_:
+        rep_dict_iter, reduced_temporal_scale, numpy.info_dict = agg_hierarchial(scales = scenario.scales, scale_level=scale_level, periods=i,
+                                 cost_factor= scenario.cost_factor, capacity_factor= scenario.capacity_factor, \
+                                     demand_factor= scenario.demand_factor, include= include)
+        wcss_list.append(numpy.info_dict['wcss_sum'])
+        
+        
+    # wcss_list = [agg_hierarchial(scales = scenario.scales, scale_level=scale_level, periods=i,
+    #                              cost_factor= scenario.cost_factor[location.name], capacity_factor= scenario.capacity_factor[location.name], \
+    #                                  demand_factor= scenario.demand_factor[location.name], include= include) for i in iter_]
+    elbow = 0
+    elbow_y = 0
+    
+    if fit == Fit.poly2:
+        theta  = numpy.polyfit(x= range_list,  y= wcss_list, deg=2)
+        y_line = [theta[2] + theta[1] * pow(x, 1) + theta[0] * pow(x, 2) for x in range_list]
+        y_slope = [theta[1] + 2*theta[0] * pow(x, 1) for x in range_list]
+
+        
+    if fit == Fit.poly3:
+        theta  = numpy.polyfit(x= range_list,  y= wcss_list, deg=3)
+        y_line = [theta[3] + theta[2] * pow(x, 1) + theta[1] * pow(x, 2) + theta[0] * pow(x, 3) for x in range_list]
+        y_slope = [theta[2] + 2*theta[1] * pow(x, 1) + 3*theta[0] * pow(x, 2) for x in range_list]
+
+    # fig, ax = plt.subplots(figsize=(8, 6))
+    # x = range_list
+    
+    # ax.plot(x, y_line, label = 'MARS fit', color = 'steelblue', alpha = 0.6)
+    
+    # ax.scatter(x, wcss_list, color = 'indianred')
+    # plt.axvline(x = elbow, alpha = 0.6, linestyle = 'dotted', label = f"elbow at {elbow}", color = 'slategrey', zorder = 3)    
+
+    # included = ''.join([str(i).split('IncludeAHC.')[1] + str(' ') for i in include])
+    
+    
+    # plt.title(f'Clustering using AHC for Houston for {included}')
+    # plt.xlabel('Cluster Size')
+    # plt.ylabel('WCSS')
+    # plt.grid(alpha=0.3)
+
+    # plt.legend()
+    
+    
+    
+    return wcss_list
+
diff --git a/src/energiapy/aggregation/dtw.py b/src/energiapy/aggregation/dtw.py
new file mode 100644
index 00000000..1dba4413
--- /dev/null
+++ b/src/energiapy/aggregation/dtw.py
@@ -0,0 +1,135 @@
+"""Clustering with Dynamic Time Warping (DTW)
+"""
+
+__author__ = "Rahul Kakodkar"
+__copyright__ = "Copyright 2023, Multi-parametric Optimization & Control Lab"
+__credits__ = ["Rahul Kakodkar", "Efstratios N. Pistikopoulos"]
+__license__ = "Open"
+__version__ = "0.0.1"
+__maintainer__ = "Rahul Kakodkar"
+__email__ = "cacodcar@tamu.edu"
+__status__ = "Production"
+
+
+import pandas
+import numpy
+from itertools import product
+from typing import Union, Tuple
+from ..components.scenario import Scenario
+from ..components.resource import Resource
+from ..components.process import Process
+from ..components.temporal_scale import Temporal_scale
+from ..components.network import Network
+from ..components.location import Location
+import matplotlib.pyplot as plt
+from enum import Enum, auto
+from ..utils.cluster_utils import dynamic_warping_matrix, dynamic_warping_path
+
+
+class IncludeDTW(Enum):
+    cost = auto()
+    demand = auto()
+    capacity = auto()
+
+def dynamic_warping(source_scenario: Scenario, target_scenario: Scenario, scale_level: int, include: list, aspect: Union[Resource, Process], reference_dict: dict = None):
+    """Dynamic time warping for scenario reconciliation
+
+    Args:
+        source_scenario (Scenario): scenario to warp from
+        target_scenario (Scenario): scenario to warp onto
+        scale_level (int): the scale level
+        include (list): which factors to include
+        aspect (Union[Resource, Process]): _description_
+        reference_dict (dict, optional): dictionary to pull values from. Defaults to None.
+
+    Returns:
+        _type_: _description_
+    """
+    
+    source_location = list(source_scenario.location_set)[0] 
+    target_location = list(target_scenario.location_set)[0]
+
+    if IncludeDTW.cost in include:
+        source_series = list(source_scenario.cost_factor[source_location.name][aspect.name].values())
+        target_series = list(target_scenario.cost_factor[target_location.name][aspect.name].values())
+        
+        reference_keys = target_scenario.cost_factor[target_location.name][aspect.name].keys()
+    
+    elif IncludeDTW.capacity in include:    
+        source_series = list(source_scenario.capacity_factor[source_location.name][aspect.name].values())
+        target_series = list(target_scenario.capacity_factor[target_location.name][aspect.name].values())
+        
+        reference_keys = target_scenario.capacity_factor[target_location.name][aspect.name].keys()
+        
+
+    elif IncludeDTW.demand in include:
+        source_series = list(source_scenario.demand_factor[source_location.name][aspect.name].values())
+        target_series = list(target_scenario.demand_factor[target_location.name][aspect.name].values())
+        
+        reference_keys = target_scenario.demand_factor[target_location.name][aspect.name].keys()
+        
+    matrix = dynamic_warping_matrix(series1= source_series, series2= target_series)
+    
+    path = dynamic_warping_path(matrix= matrix)
+    
+    path = [i for i in path if i[0] != 0] #remove zeroth value
+    
+
+    
+    #collects the x and y coordinates of path as separate lists
+    x_ = [path[i][0] -1 for i in range(len(path))] 
+    y_ = [path[i][1] -1 for i in range(len(path))] 
+    
+    
+    #reverse the coordinates 
+    x_.reverse() 
+    y_.reverse()
+    
+    # xr = x_[:-1]
+    # yr = y_[:-1]
+    
+    
+    i_list = list(range(len(target_series)))
+    
+    
+    j_list = list(reference_keys)
+    source_values = [source_series[i] for i in x_]
+ 
+    
+    target_values = [target_series[i] for i in y_]
+
+    # print(len(target_values))
+
+
+    # reduced_temporal_scale = Temporal_scale(discretization_list=[1, len(target_values), 24])
+    
+    # iter_temporal_scale = target_scenario.scales
+    
+    reduced_temporal_scale = target_scenario.scales
+    
+    reduced_scenario_scaleiter = [(i) for i in product(*[reduced_temporal_scale.scale[i] for i in reduced_temporal_scale.scale])]
+    
+    
+    #TODO Check this 
+    counts = {j:  y_.count(i) for i,j in zip(i_list, j_list)}
+    
+    # print(counts)
+    
+    # #TODO find actual rep_day
+    # if reference_dict is not None:
+    rep_dict = {j: {'rep_period': reference_dict[j]['rep_period'], 'cluster_wt': counts[j[:scale_level]]} for j in reduced_scenario_scaleiter}
+    #     # rep_dict = {j: {'rep_period': j, 'cluster_wt': 1} for j in reduced_scenario_scaleiter}
+    # else:
+    # rep_dict = {j: {'rep_period': j, 'cluster_wt': counts[j[:scale_level]]} for j in reduced_scenario_scaleiter}
+            
+    
+    info_dict = {
+        'matrix': matrix,
+        'path': path,
+        'input_data': {'source': source_series, 'target': target_series},
+        'output_data': {'source': source_values, 'target': target_values}  
+    }
+    
+    return rep_dict, reduced_temporal_scale, info_dict
+
+
diff --git a/src/energiapy/aggregation/reduce_scenario.py b/src/energiapy/aggregation/reduce_scenario.py
new file mode 100644
index 00000000..3cb4cd0d
--- /dev/null
+++ b/src/energiapy/aggregation/reduce_scenario.py
@@ -0,0 +1,107 @@
+"""Reduce the scenario with appropriate method
+"""
+
+__author__ = "Rahul Kakodkar"
+__copyright__ = "Copyright 2023, Multi-parametric Optimization & Control Lab"
+__credits__ = ["Rahul Kakodkar", "Efstratios N. Pistikopoulos"]
+__license__ = "Open"
+__version__ = "0.0.1"
+__maintainer__ = "Rahul Kakodkar"
+__email__ = "cacodcar@tamu.edu"
+__status__ = "Production"
+
+
+import pandas
+import numpy
+from itertools import product
+from typing import Union, Tuple
+from ..components.scenario import Scenario
+from ..components.resource import Resource
+from ..components.process import Process
+from ..components.temporal_scale import Temporal_scale
+from ..components.network import Network
+from ..components.location import Location
+from ..aggregation.ahc import agg_hierarchial
+from ..aggregation.dtw import dynamic_warping
+import matplotlib.pyplot as plt
+from enum import Enum, auto
+
+
+class Clustermethod(Enum):
+    agg_hierarchial = auto()
+    dynamic_warping = auto()
+
+
+def reduce_scenario(scenario: Scenario, method: Clustermethod, include: list, scale_level: int = None, source_scenario: Scenario = None, target_scenario: Scenario= None, aspect: Union[Resource, Process] = None, location: Location = None, periods: int = None, reference_dict: dict = None) -> Scenario:
+    """reduce scenario using a particular method
+
+    Args:
+        scenario (Scenario): full-scale scenario
+        method (Clustermethod): cluster method to use
+        include (list): list of factors to include
+        scale_level (int): the scale level
+        source_scenario (Scenario): scenario to warp from
+        target_scenario (Scenario): scenario to warp onto
+        aspect (Union[Resource, Process]): aspect for dtw to reconcile over
+        location (Location): location in scenario to cluster
+        periods (int): number of clustering intervals
+        reference_dict (dict, optional): dictionary to pull values from. Defaults to None.
+
+
+    Returns:
+        Scenario: reduced scenario
+    """
+
+    if method is Clustermethod.agg_hierarchial:
+        rep_dict, reduced_temporal_scale, numpy.info_dict = agg_hierarchial(scenario.scales, scale_level=scale_level, periods=periods,
+                                                                     cost_factor=scenario.cost_factor, capacity_factor=scenario.capacity_factor,
+                                                                     demand_factor = scenario.demand_factor, include = include)
+        
+
+        
+    if method is Clustermethod.dynamic_warping:
+        rep_dict, reduced_temporal_scale, numpy.info_dict = dynamic_warping(source_scenario = source_scenario, \
+            target_scenario= target_scenario, include= include, aspect= aspect, scale_level= scale_level, reference_dict = reference_dict)
+
+    
+    reduced_scenario_scaleiter = [(i) for i in product(
+        *[reduced_temporal_scale.scale[i] for i in reduced_temporal_scale.scale])]
+    
+    # cluster_wt = {scale: rep_dict[scale]['cluster_wt'] for scale in reduced_scenario_scaleiter}    
+                
+        
+    reduced_scenario = Scenario(name=f"{scenario.name}_reduced", scales=reduced_temporal_scale,
+                                network=scenario.network, expenditure_scale_level=scenario.expenditure_scale_level,
+                                scheduling_scale_level=scenario.scheduling_scale_level, network_scale_level=scenario.network_scale_level,
+                                demand_scale_level=scenario.demand_scale_level, \
+                                    label=f"{scenario.label}(reduced)")
+    for location in scenario.location_set:
+        if scenario.cost_factor[location.name] is not None:
+            len_ = len(list(list(scenario.cost_factor[location.name].values())[0].keys())[0])
+            reduced_scenario.cost_factor[location.name] = {i: {
+                j[:len_]: scenario.cost_factor[location.name][i][rep_dict[j]['rep_period'][:len_]] \
+                    for j in list(rep_dict.keys())} for i in list(scenario.cost_factor[location.name])}
+
+        if scenario.capacity_factor[location.name] is not None:
+            len_ = len(list(list(scenario.capacity_factor[location.name].values())[0].keys())[0])
+            reduced_scenario.capacity_factor[location.name]  = {i: {
+                j[:len_]: scenario.capacity_factor[location.name][i][rep_dict[j]['rep_period'][:len_]] \
+                    for j in list(rep_dict.keys())} for i in list(scenario.capacity_factor[location.name])}
+
+        if scenario.demand_factor[location.name] is not None:
+            len_ = len(list(list(scenario.demand_factor[location.name].values())[0].keys())[0])
+            reduced_scenario.demand_factor[location.name]  =  {i: {
+                j[:len_]: scenario.demand_factor[location.name][i][rep_dict[j]['rep_period'][:len_]] \
+                    for j in list(rep_dict.keys())} for i in list(scenario.demand_factor[location.name])}
+
+
+    
+    if reference_dict is not None:
+        reduced_scenario.cluster_wt = {scale: reference_dict[scale]['cluster_wt'] for scale in reduced_scenario_scaleiter}
+    
+    else:    
+    
+        reduced_scenario.cluster_wt = {scale: rep_dict[scale]['cluster_wt'] for scale in reduced_scenario_scaleiter}
+
+    
+    return reduced_scenario, rep_dict, numpy.info_dict
diff --git a/src/energiapy/components/location.py b/src/energiapy/components/location.py
index be67bdf6..399716d3 100644
--- a/src/energiapy/components/location.py
+++ b/src/energiapy/components/location.py
@@ -23,6 +23,7 @@
 from ..utils.scale_utils import scale_changer
 from pandas import DataFrame
 from enum import Enum, auto
+from warnings import warn
 
 
 
@@ -59,6 +60,7 @@ class Location:
     demand_scale_level: int = 1
     cost_scale_level: int = 1
     capacity_scale_level: int = 1
+    land_cost: float = 0
     label: str = ''
     
     def __post_init__(self):
@@ -80,22 +82,29 @@ def __post_init__(self):
         self.materials = self.get_materials()
         self.scale_levels = self.scales.scale_levels
         self.processes_full = self.processes.union({create_storage_process(i) for i in self.processes if i.processmode == ProcessMode.storage})
+        self.prod_max = self.get_prod_max()
         
         if self.capacity_factor is not None:
             self.varying_capacity = set(self.capacity_factor.keys())
             if isinstance(list(self.capacity_factor.values())[0], DataFrame):
                 self.capacity_factor = scale_changer(self.capacity_factor, scales=self.scales, scale_level=self.capacity_scale_level)
-        
+            else:
+                warn('Input should be a dict of a DataFrame, Dict[Process, float]')
+                
         if self.cost_factor is not None:
             self.varying_cost = set(self.cost_factor.keys())
             if isinstance(list(self.cost_factor.values())[0], DataFrame):
                 self.cost_factor = scale_changer(self.cost_factor, scales=self.scales, scale_level=self.cost_scale_level)
-        
+            else:
+                warn('Input should be a dict of a DataFrame, Dict[Resource, float]')
+                
         if self.demand_factor is not None:    
             self.varying_demand = set(self.demand_factor.keys())
             if isinstance(list(self.demand_factor.values())[0], DataFrame):
                 self.demand_factor = scale_changer(self.demand_factor, scales=self.scales, scale_level=self.demand_scale_level)
-       
+            else:
+                warn('Input should be a dict of a DataFrame, Dict[Resource, float]')
+                
         self.resource_price = self.get_resource_price()   
         self.failure_processes = self.get_failure_processes()
         self.fail_factor = self.make_fail_factor()
@@ -112,7 +121,7 @@ def get_resources(self) -> Set[Resource]:
             resources_single = set().union(*[set(i.conversion.keys()) for i in self.processes if i.processmode == ProcessMode.single])
             resources_multi = set()
             for i in [i for i in self.processes if i.processmode == ProcessMode.multi]:
-                resources_multi  = resources_multi.union(*[set(j.keys()) for j in list(i.multiconversion.values())])
+                resources_multi  = resources_multi.union(*[set(j.keys()) for j in list(i.conversion.values())])
             return resources_single.union(resources_multi)
     
     def get_materials(self) -> Set[Material]:
@@ -156,7 +165,17 @@ def make_fail_factor(self)-> dict:
             scale_iter = [(i) for i in product(self.scales.scale[0], self.scales.scale[1], self.scales.scale[2])]
             fail_factor = {process_.name: {(scale_): sample([0]*int(process_.p_fail*100) +  [1] *int((1- process_.p_fail)*100), 1)[0] for scale_ in   scale_iter} for process_ in self.failure_processes}
             return fail_factor
-            
+        
+    def get_prod_max(self) -> dict:
+        prod_max_dict = dict()
+        for i in self.processes_full:
+            if i.processmode == ProcessMode.multi:
+                prod_max_dict[i.name] = i.prod_max
+            else:
+                prod_max_dict[i.name] = {0: None}
+                prod_max_dict[i.name][0] = i.prod_max
+        return prod_max_dict
+    
     def __repr__(self):
         return self.name
     
diff --git a/src/energiapy/components/network.py b/src/energiapy/components/network.py
index ae80224d..ae819d58 100644
--- a/src/energiapy/components/network.py
+++ b/src/energiapy/components/network.py
@@ -61,6 +61,7 @@ def __post_init__(self):
         self.transport_avail_dict = self.make_transport_avail_dict()
         self.locations = list(
             set(self.source_locations).union(set(self.sink_locations)))
+        
 
     def make_distance_dict(self) -> dict:
         """returns a dictionary of distances from sources to sinks
@@ -92,5 +93,5 @@ def make_transport_avail_dict(self) -> dict:
             i: {j.name for j in self.transport_dict[i]} for i in self.transport_dict.keys()}
         return transport_avail_dict
 
-    def __refr__(self):
+    def __repr__(self):
         return self.name
diff --git a/src/energiapy/components/process.py b/src/energiapy/components/process.py
index 568cab4a..89a15087 100644
--- a/src/energiapy/components/process.py
+++ b/src/energiapy/components/process.py
@@ -1,4 +1,3 @@
-#%%
 """Process data class  
 """
 
@@ -19,6 +18,8 @@
 import pandas
 from random import sample
 from enum import Enum, auto
+from warnings import warn
+
 
 class CostDynamics(Enum):
     constant = auto()
@@ -30,9 +31,10 @@ class ProcessMode(Enum):
     multi = auto() # allows multiple modes
     storage = auto()
 
-class UncertainProcess(Enum):
-    capacity = auto() #if capacity is uncertain
-
+class VaryingProcess(Enum):
+    deterministic_capacity = auto() #if capacity is uncertain
+    uncertain_capacity = auto()
+    
 @dataclass
 class Process:
     """
@@ -40,8 +42,7 @@ class Process:
 
     Args:
         name (str): name of process, short ones are better to deal with.
-        conversion (Dict[resource, float], optional): conversion data. Defaults to None.
-        multiconversion (Dict[int,Dict[Resource, float]], optional): Multiple modes for conversion can be passed as dictionaries. Defaults to None. 
+        conversion (Union[Dict[int,Dict[Resource, float]], Dict[Resource, float]], optional): conversion data (Dict[Resource, float]]), if multimode the of form Dict[int,Dict[Resource, float]]. Defaults to None.
         introduce (int, optional): Time period in the network scale when introduced. Defaults to 0.
         retire (int, optional): Time period in the network scale when retired. Defaults to None.
         capex (Union[float, dict], None): Capital expenditure per unit basis, can be scaled with capacity. Defaults to None.
@@ -59,13 +60,12 @@ class Process:
         block (str, optional): define block for convenience. Defaults to None.
         citation (str, optional): citation for data. Defaults to 'citation needed'.
         lifetime (float, optional): the expected lifetime of process. Defaults to None.
-        varying (bool, optional): whether process is subject to uncertainty. Defaults to False.
+        varying (VaryingProcess, optional): whether process is subject to uncertainty. Defaults to False.
         p_fail (float, optional): failure rate of process. Defaults to None.
         label(str, optional):Longer descriptive label if required. Defaults to ''
         storage(list, optional): Resource that can be stored in process.
         storage_loss (float, optional): If storage process, storage loss experienced per time period in scheduling horizon. Defaults to 0. 
-        uncertain (UncertainProcess, optional): the type of uncertainty the process experiences. Defaults to None.
-
+        
     Examples:
         For processes with varying production capacity
         
@@ -73,25 +73,24 @@ class Process:
 
         For process with multiple modes of operation
         
-        >>> PEM = Process(name = 'PEM', multiconversion = {1: {Power: -1, H2: 19.474, O2: 763.2, H2O: -175.266}, 2: {Power: -1, H2: 1.2*19.474, O2: 1.2*763.2, H2O: 1.2*-175.266}, prod_max= 100, capex = 784000, label = 'PEM Electrolysis')
+        >>> PEM = Process(name = 'PEM', conversion = {1: {Power: -1, H2: 19.474, O2: 763.2, H2O: -175.266}, 2: {Power: -1, H2: 1.2*19.474, O2: 1.2*763.2, H2O: 1.2*-175.266}, prod_max= 100, capex = 784000, label = 'PEM Electrolysis')
         
         For process with multiple resource inputs
         
-        >>> CoolCar = Process(name = 'CoolCar', multiconversion = {1: {Power: -1, Mile: 1}, 2: {H2: -1, Mile:2}, prod_max= 50, capex = 70, label = 'CoolCar')
+        >>> CoolCar = Process(name = 'CoolCar', conversion = {1: {Power: -1, Mile: 1}, 2: {H2: -1, Mile:2}, prod_max= 50, capex = 70, label = 'CoolCar')
         
     """
 
     name: str 
     introduce: int = 0
     retire: int = None
-    conversion: Dict[Resource, float] = None
-    multiconversion: Dict[int,Dict[Resource, float]] = None 
+    conversion: Union[Dict[int,Dict[Resource, float]], Dict[Resource, float]] = None
     capex: Union[float, dict] = None
     fopex: Union[float, dict] = None
     vopex: Union[float, dict] = None
     incidental: float = None
     material_cons: Dict[Material, float] = None
-    prod_max: float = 0
+    prod_max: Union[Dict[int,float], float] = 0
     prod_min: float = 0
     basis: str = 'unit'
     carbon_credit: float = 0
@@ -101,12 +100,14 @@ class Process:
     block: str = ''
     citation: str = 'citation needed'
     lifetime: int = None
-    varying:bool = False
+    varying: VaryingProcess = None
     p_fail: float = None
     label: str = ''
     storage: Resource = None
     storage_loss: float = 0
-    uncertain: UncertainProcess = None
+    store_max: float = 0
+    store_min: float = 0
+
     
     
     def __post_init__(self):
@@ -118,13 +119,9 @@ def __post_init__(self):
             conversion_discharge (Dict[Resource, float]): Creates a dictionary with the discharge conversion values (considers storage loss).
             cost_dynamics (CostDynamics): Determines whether the cost scales linearly with the unit capacity, or is a piecewise-linear function.
         """
-        if self.multiconversion is not None:
-            self.processmode = ProcessMode.multi
-        elif self.conversion is not None:
-            self.processmode = ProcessMode.single
-            
+
         if self.storage is not None:
-            self.resource_storage= create_storage_resource(process_name= self.name, resource= self.storage, store_max= self.prod_max, store_min= self.prod_min)
+            self.resource_storage= create_storage_resource(process_name= self.name, resource= self.storage, store_max= self.store_max, store_min= self.store_min)
             self.conversion = {self.storage:-1, self.resource_storage:1}
             self.conversion_discharge = {self.resource_storage:-1, self.storage:1*(1- self.storage_loss)}
             self.processmode = ProcessMode.storage
@@ -132,12 +129,25 @@ def __post_init__(self):
         else:
             self.conversion_discharge = None
             self.resource_storage = None
-    
+            if type(list(self.conversion.keys())[0]) is int:
+                self.processmode = ProcessMode.multi
+            else:
+                self.processmode = ProcessMode.single
+                
         if type(self.capex) is dict:
             self.cost_dynamics = CostDynamics.pwl
         else:
             self.cost_dynamics = CostDynamics.constant      
-                    
+        
+        if self.processmode is ProcessMode.multi:
+            self.resource_req = {i.name for i in self.conversion[list(self.conversion.keys())[0]].keys()}
+        else:
+            self.resource_req = {i.name for i in self.conversion.keys()}     
+    
+        if self.processmode == ProcessMode.multi:    
+            if list(self.prod_max.keys()) != list(self.conversion.keys()):
+                warn('The keys for prod_max and conversion need to match if ProcessMode.multi')
+                            
     def __repr__(self):
         return self.name
     
diff --git a/src/energiapy/components/resource.py b/src/energiapy/components/resource.py
index ee86da9e..8db97c02 100644
--- a/src/energiapy/components/resource.py
+++ b/src/energiapy/components/resource.py
@@ -14,10 +14,14 @@
 from dataclasses import dataclass
 import pandas
 from enum import Enum, auto
+from typing import Union
 
-class UncertainResource(Enum):
-    demand = auto()
-    price = auto()
+class VaryingResource(Enum):
+    deterministic_demand = auto()
+    deterministic_price = auto()
+    uncertain_demand = auto()
+    uncertain_price = auto()
+    
 
 @dataclass
 class Resource:
@@ -35,12 +39,11 @@ class Resource:
         sell (bool, optional): True if resource can be discharged. Defaults to False.
         demand (bool, optional): True, if the process has to meet specific demand. If True, sell defaults to True. Defaults to False.
         basis (str, optional): Unit basis for the resource. Defaults to 'unit'.
-        block (str, optional): Assign a block for categorization. Defaults to None.
+        block (Union[str, list, dict], optional): Assign a block for categorization. Defaults to None.
         citation (str, optional): Add citations for data sources. Defaults to 'citation needed'.
         varying (bool, optional): If the cost of resource is varying/uncertain. Defaults to False.
         label (str, optional): Longer descriptive label if required. Defaults to ''.
         gwp (float, optional): Global Warming Potential per unit consumption of resource. Defaults to 0.
-        uncertain (UncertainResource, optional): the type of uncertainty the resource experiences. Defaults to None.
 
         
     Examples:
@@ -68,13 +71,11 @@ class Resource:
     sell: bool = False
     demand: bool = False
     basis: str = 'unit'
-    block: str = ''
+    block: Union[str,list,dict] = ''
     citation: str = 'citation needed'
-    varying: bool = False
+    varying: VaryingResource = None
     label: str = ''
     gwp: float = 0
-    uncertain: UncertainResource = None
-
 
     def __post_init__(self):
         if self.demand is True:
diff --git a/src/energiapy/components/scenario.py b/src/energiapy/components/scenario.py
index 38695177..a13b5a95 100644
--- a/src/energiapy/components/scenario.py
+++ b/src/energiapy/components/scenario.py
@@ -11,11 +11,12 @@
 __status__ = "Production"
 
 from pandas import DataFrame
+import numpy
 from ..components.network import Network
 from ..components.location import Location
 from ..components.temporal_scale import Temporal_scale
-from ..components.process import Process, ProcessMode, UncertainProcess
-from ..components.resource import Resource, UncertainResource 
+from ..components.process import Process, ProcessMode, VaryingProcess
+from ..components.resource import Resource, VaryingResource 
 from ..model.constraints import *
 from ..utils.math_utils import scaler, find_euclidean_distance, generate_connectivity_matrix
 from sklearn.cluster import AgglomerativeClustering
@@ -144,7 +145,11 @@ def __post_init__(self):
         self.conversion = {i.name: {j.name: i.conversion[j] if j in i.conversion.keys() \
             else 0 for j in self.resource_set} for i in self.process_set if i.conversion is not None}
 
-        self.prod_max = {i.name: {j.name: j.prod_max for j in i.processes_full} for i in self.location_set}
+        
+        if type(self.demand) is dict:
+            self.demand = {location.name: {resource.name: self.demand[location][resource] for resource in self.demand[location].keys()} for location in self.demand.keys()}
+        
+        self.prod_max = {i.name: i.prod_max for i in self.location_set}
         self.prod_min = {i.name: {j.name: j.prod_min for j in i.processes_full} for i in self.location_set}
         self.cons_max = {i.name: {j.name: j.cons_max for j in i.resources_full} for i in self.location_set}
         self.store_max = {i.name: {j.name: j.store_max for j in i.resources_full} for i in self.location_set}
@@ -164,60 +169,200 @@ def __post_init__(self):
         self.material_gwp_dict = {i.name:{j.name: j.gwp for j in self.material_set} for i in self.location_set}
         self.resource_gwp_dict = {i.name:{j.name: j.gwp for j in self.resource_set} for i in self.location_set}
         self.process_gwp_dict = {i.name: {j.name: j.gwp for j in self.process_set} for i in self.location_set}
+        self.land_cost_dict = {i.name: i.land_cost for i in self.location_set}
         self.fail_factor = {i.name: i.fail_factor for i in self.location_set}
-        self.process_resource_dict = {i.name: {j.name for j in i.conversion.keys() } for i in self.process_set if i.conversion is not None}
+        
+        
+        self.process_resource_dict = {i.name: i.resource_req for i in self.process_set}
+        
         self.process_material_dict = {i.name: {j.name: i.material_cons[j] for j in i.material_cons.keys() } for i in self.process_set if i.material_cons is not None}
-        self.mode_dict = {i.name: [j for j in list(i.multiconversion.keys())] for i in self.process_set if i.processmode == ProcessMode.multi }
-    
 
+        multiconversion_dict = dict()
+        for i in self.process_set:
+            if i.processmode == ProcessMode.multi:
+                multiconversion_dict[i.name] = {j: None for j in i.conversion.keys()}
+                for k in list(multiconversion_dict[i.name].keys()):
+                    multiconversion_dict[i.name][k] = {j.name: i.conversion[k][j] if j in i.conversion[k].keys() else 0 for j in self.resource_set}
+            else:
+                multiconversion_dict[i.name] = {0: None}
+                multiconversion_dict[i.name][0] = {j.name: i.conversion[j]  if j in i.conversion.keys() else 0 for j in self.resource_set}
+        
+        self.multiconversion = multiconversion_dict
+                
+   
+        self.mode_dict = {i.name: [j for j in list(self.multiconversion[i.name].keys())] for i in self.process_set}
+    
         self.set_dict ={
             'resources': [i.name for i in self.resource_set],
             'resources_nosell': [i.name for i in self.resource_set if i.sell ==  False],
             'resources_sell': [i.name for i in self.resource_set if i.sell ==  True], 
             'resources_store': [i.name for i in self.resource_set if i.store_max >0],
             'resources_purch': [i.name for i in self.resource_set if i.cons_max > 0],
-            'resources_varying': [i.name for i in self.resource_set if i.varying == True],
+            'resources_varying_demand': [i.name for i in self.resource_set if i.varying == VaryingResource.deterministic_demand],
+            'resources_varying_price': [i.name for i in self.resource_set if i.varying == VaryingResource.deterministic_price],
             'resources_demand': [i.name for i in self.resource_set if i.demand == True],
-            'resources_uncertain_price': [i.name for i in self.resource_set if i.uncertain == UncertainResource.price],
-            'resources_uncertain_demand': [i.name for i in self.resource_set if i.uncertain == UncertainResource.demand],
+            'resources_certain_price': [i.name for i in self.resource_set if (i.varying is None) and (i.cons_max >0)],
+            'resources_uncertain_price': [i.name for i in self.resource_set if i.varying == VaryingResource.uncertain_price],
+            'resources_certain_demand': [i.name for i in self.resource_set if (i.varying is None) and (i.demand == True)],
+            'resources_uncertain_demand': [i.name for i in self.resource_set if i.varying == VaryingResource.uncertain_demand],
             'processes': [i.name for i in self.process_set],
             'processes_full': list(self.conversion.keys()),
-            'processes_varying': [i.name for i in self.process_set if i.varying == True],
+            'processes_varying': [i.name for i in self.process_set if i.varying == VaryingProcess.deterministic_capacity],
             'processes_failure': [i.name for i in self.process_set if i.p_fail is not None],
             'processes_materials': [i.name for i in self.process_set if i.material_cons is not None],
             'processes_storage': [i.name for i in self.process_set if i.conversion_discharge is not None],
             'processes_multim': [i.name for i in self.process_set if i.processmode == ProcessMode.multi],
             'processes_singlem': [i.name for i in self.process_set if (i.processmode == ProcessMode.single) or (i.processmode == ProcessMode.storage)],
-            'processes_certain_capacity': [i.name for i in self.process_set if i.uncertain is None],
-            'processes_uncertain_capacity': [i.name for i in self.process_set if i.uncertain == UncertainProcess.capacity],
-            'locations': [i.name for i in self.location_set]
+            'processes_certain_capacity': [i.name for i in self.process_set if i.varying is None],
+            'processes_uncertain_capacity': [i.name for i in self.process_set if i.varying == VaryingProcess.uncertain_capacity],
+            'locations': [i.name for i in self.location_set],
+            'materials': [i.name for i in self.material_set]
             }
         
         if self.source_locations is not None:
-            self.set_dict['sources']: [i.name for i in self.source_locations if self.source_locations is not None]
+            self.set_dict['sources'] = [i.name for i in self.source_locations]
         else:
-            self.set_dict['sources']: []      
+            self.set_dict['sources'] =  []      
             
         if self.sink_locations is not None:
-            self.set_dict['sinks']: [i.name for i in self.sink_locations]
+            self.set_dict['sinks'] = [i.name for i in self.sink_locations]
         else:
-            self.set_dict['sinks']: []
+            self.set_dict['sinks'] = []
             
         if self.material_set is not None:
-            self.set_dict['materials']: [i.name for i in self.material_set]
+            self.set_dict['materials'] = [i.name for i in self.material_set]
         else:
-            self.set_dict['materials']: []
+            self.set_dict['materials'] =[]
             
         if self.transport_set is not None:
-            self.set_dict['transports']: [i.name for i in self.transport_set]
-            self.set_dict['resources_trans']: [i.name for i in set().union(*[i.resources for i in self.transport_set])]
+            self.set_dict['transports'] = [i.name for i in self.transport_set]
+            self.set_dict['resources_trans'] = [i.name for i in set().union(*[i.resources for i in self.transport_set])]
         else:
-            self.set_dict['transports']: []
-            self.set_dict['resources_trans']: []
+            self.set_dict['transports'] =  []
+            self.set_dict['resources_trans'] = []
 
     def make_conversion_df(self):
-        return DataFrame.from_dict(self.conversion)
+        return DataFrame.from_dict(self.conversion).transpose()
+    
+        
+    def matrix_form(self):
+        """returns matrices for the scenario.
+        
+        Returns:
+            tuple: A, b, c, H, CRa, CRb, F
+        """
+        
+        
+        if len(self.location_set) > 1:
+            "can only do this for a single location scenario"
+        else:
+            location = list(self.location_set)[0].name
+            
+            #find number of different variables
+            
+            n_Inv = len(self.set_dict['resources_store'])
+            n_Sf = len(self.set_dict['resources_certain_demand'])
+            n_Cf = len(self.set_dict['resources_certain_price'])
+            n_Pf = len(self.set_dict['processes_certain_capacity'])
+            n_S = len(self.set_dict['resources_uncertain_demand'])
+            n_C = len(self.set_dict['resources_uncertain_price'])
+            n_P = len(self.set_dict['processes_uncertain_capacity'])
+            n_bal = n_P + n_Pf
+
+            n_vars_fix = n_Inv + n_Sf + n_Cf + n_Pf
+            n_vars_theta = n_S + n_C + n_P 
+            n_vars = n_vars_fix + n_vars_theta
+            
+            #make b matrix
+            
+            b_bal = numpy.zeros((n_bal,1))
+            b_Inv = numpy.array([[self.store_max[location][i]] for i in self.set_dict['resources_store']]) 
+            b_Sf = numpy.array([[-self.demand[location][i]] for i in self.set_dict['resources_certain_demand']])
+            b_Cf = numpy.array([[self.cons_max[location][i]] for i in self.set_dict['resources_certain_price']])
+            b_Pf = numpy.array([[self.prod_max[location][i]] for i in self.set_dict['processes_certain_capacity']]) 
+
+            b_S = numpy.array([[-self.demand[location][i]] for i in self.set_dict['resources_uncertain_demand']])
+            b_C = numpy.array([[self.cons_max[location][i]] for i in self.set_dict['resources_uncertain_price']])
+            b_P = numpy.array([[self.prod_max[location][i]] for i in self.set_dict['processes_uncertain_capacity']])
+            b_nn = numpy.zeros((n_vars,1))
+
+            b_list = [b_bal, b_Inv, b_Sf, b_Cf, b_Pf, b_S, b_C, b_P, b_nn ]
+
+            b = numpy.block([[i] for i in b_list if len(i)>0])
+            
+            
+            #make F matrix
+
+            F = numpy.zeros((len(b), n_vars_theta))
+
+            iter_ = 0
+            for i in range(n_S):
+                F[n_bal + n_vars_fix + iter_][i] = self.demand[location][self.set_dict['resources_uncertain_demand'][i]]
+                iter_+= 1
 
+            iter_ = 0
+            for i in range(n_C):
+                F[n_bal + n_vars_fix + n_S + iter_][n_S + i] = self.cons_max[location][self.set_dict['resources_uncertain_price'][i]]
+                iter_+= 1
+
+
+            iter_ = 0
+            for i in range(n_P):
+                F[n_bal + n_vars_fix + n_S + n_C + iter_][n_S + n_C + i] = self.prod_max[location][self.set_dict['processes_uncertain_capacity'][i]]
+                iter_+= 1
+            
+
+            #make A matrix
+            print(n_Inv, n_Sf, n_Cf, n_S, n_C)
+            
+            A_bal = numpy.diag([*[-1]*n_Inv, *[-1]*n_Sf, *[1]*n_Cf, *[-1]*n_S, *[1]*n_C])
+
+            A_conv = numpy.array([[self.conversion[i][j] for j in self.conversion[i].keys()] for i in self.conversion.keys() ]).transpose()
+
+            A_diag = numpy.diag([*[-1]*n_Inv, *[-1]*n_Sf, *[1]*n_Cf, *[1]*n_Pf, *[-1]*n_S, *[1]*n_C, *[1]*n_P])
+
+            A_nn = numpy.eye(n_vars)
+            
+            print(A_bal)
+            
+            print(A_conv)
+            
+            print(A_diag)
+            
+            print(A_nn)
+
+            A = numpy.block([ [numpy.block([A_bal, A_conv])], [A_diag],  [-A_nn]])
+            
+        
+            #make c matrix
+            
+            
+            c_Inv = numpy.zeros((n_Inv,1))
+            c_Sf = numpy.zeros((n_Sf,1))
+            c_Cf = numpy.zeros((n_Cf,1))
+            c_Pf = numpy.array([[self.capex_dict[i]] for i in self.set_dict['processes_certain_capacity']])
+
+            c_S = numpy.zeros((n_S,1))
+            c_C = numpy.zeros((n_C,1))
+            c_P = numpy.array([[self.capex_dict[i]] for i in self.set_dict['processes_uncertain_capacity']])
+
+            c_list = [c_Inv, c_Sf, c_Cf, c_Pf, c_S, c_C, c_P]
+            c = numpy.block([[i] for i in c_list if len(i) > 0 ])
+            
+            
+            #make H matrix
+                
+            H = numpy.zeros((A.shape[1],F.shape[1]))
+            
+            #make critical regions
+            
+            
+            CRa = numpy.vstack((numpy.eye(n_vars_theta), -numpy.eye(n_vars_theta)))
+            CRb = numpy.array([*[1]*n_vars_theta, *[0]*n_vars_theta]).reshape(n_vars_theta*2, 1)
+            
+            return A, b, c, H, CRa, CRb, F
+
+        
     def __repr__(self):
         return self.name
 
diff --git a/src/energiapy/components/transport.py b/src/energiapy/components/transport.py
index 398f7147..64d7b530 100644
--- a/src/energiapy/components/transport.py
+++ b/src/energiapy/components/transport.py
@@ -53,7 +53,7 @@ class Transport:
     label: str = '' 
 
 
-    def __refr__(self):
+    def __repr__(self):
         return self.name
     
     def __hash__(self):
diff --git a/src/energiapy/formulation/milp.py b/src/energiapy/formulation/milp.py
deleted file mode 100644
index 7046483d..00000000
--- a/src/energiapy/formulation/milp.py
+++ /dev/null
@@ -1,176 +0,0 @@
-"""Formulates a multiscale mixed integer linear programming (MILP) model from Scenario  
-"""
-
-__author__ = "Rahul Kakodkar"
-__copyright__ = "Copyright 2022, Multi-parametric Optimization & Control Lab"
-__credits__ = ["Rahul Kakodkar", "Efstratios N. Pistikopoulos"]
-__license__ = "Open"
-__version__ = "0.0.1"
-__maintainer__ = "Rahul Kakodkar"
-__email__ = "cacodcar@tamu.edu"
-__status__ = "Production"
-
-from ..components.scenario import Scenario
-from ..model.sets import generate_sets
-from ..model.variables.schedule import *
-from ..model.variables.network import *
-from ..model.variables.binary import *
-from ..model.variables.cost import *
-from ..model.variables.transport import *
-from src.energiapy.model.constraints.resource_balance import *
-from src.energiapy.model.constraints.production import *
-from src.energiapy.model.constraints.inventory import *
-from src.energiapy.model.constraints.cost import *
-from src.energiapy.model.constraints.land import *
-from src.energiapy.model.constraints.emission import *
-from src.energiapy.model.constraints.transport import *
-from ..model.objectives import cost_objective
-from pyomo.environ import ConcreteModel
-
-
-class IncludeVars(Enum):
-    network = auto()
-    schedule = auto()
-    binary = auto()
-    cost = auto()
-    transport = auto()
-    
-    
-    
-    
-class IncludeCons(Enum):
-    network = auto()
-    schedule = auto()
-    binary = auto()
-    
-
-def formulate_milp(scenario: Scenario) -> ConcreteModel:
-    """formulates a multi-scale mixed integer linear programming formulation of the scenario
-
-    Args:
-        scenario (Scenario): scenario under consideration
-
-    Returns:
-        ConcreteModel: pyomo model instance with sets, variables, constraints, objectives generated
-    """
-
-    instance = ConcreteModel()
-
-    generate_sets(instance=instance, location_set=scenario.location_set, transport_set=scenario.transport_set, scales=scenario.scales,
-                  process_set=scenario.process_set, resource_set=scenario.resource_set, material_set=scenario.material_set,
-                  source_set=scenario.source_locations, sink_set=scenario.sink_locations)
-
-    generate_scheduling_vars(
-        instance=instance, scale_level=scenario.scheduling_scale_level)
-    generate_network_vars(
-        instance=instance, scale_level=scenario.network_scale_level)
-    generate_network_binary_vars(
-        instance=instance, scale_level=scenario.network_scale_level)
-
-    if len(instance.locations) > 1:
-        generate_transport_vars(
-            instance=instance, scale_level=scenario.scheduling_scale_level)
-
-    inventory_balance_constraint(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
-                                 conversion=scenario.conversion)
-    nameplate_production_constraint(instance=instance, capacity_factor=scenario.capacity_factor,
-                                    network_scale_level=scenario.network_scale_level, scheduling_scale_level=scenario.scheduling_scale_level)
-    nameplate_inventory_constraint(instance=instance, loc_res_dict=scenario.loc_res_dict, network_scale_level=scenario.network_scale_level,
-                                   scheduling_scale_level=scenario.scheduling_scale_level)
-    resource_consumption_constraint(instance=instance, loc_res_dict=scenario.loc_res_dict,
-                                    cons_max=scenario.cons_max, scheduling_scale_level=scenario.scheduling_scale_level)
-    resource_purchase_constraint(instance=instance, cost_factor=scenario.cost_factor, price=scenario.price,
-                                 loc_res_dict=scenario.loc_res_dict, scheduling_scale_level=scenario.scheduling_scale_level,
-                                 expenditure_scale_level=scenario.expenditure_scale_level)
-    # resource_discharge_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level)
-
-    production_facility_constraint(instance=instance, prod_max=scenario.prod_max,
-                                   loc_pro_dict=scenario.loc_pro_dict, network_scale_level=scenario.network_scale_level)
-    storage_facility_constraint(instance=instance, store_max=scenario.store_max,
-                                loc_res_dict=scenario.loc_res_dict, network_scale_level=scenario.network_scale_level)
-
-    min_production_facility_constraint(instance=instance, prod_min=scenario.prod_min,
-                                       loc_pro_dict=scenario.loc_pro_dict, network_scale_level=scenario.network_scale_level)
-    min_storage_facility_constraint(instance=instance, store_min=scenario.store_min,
-                                    loc_res_dict=scenario.loc_res_dict, network_scale_level=scenario.network_scale_level)
-
-    location_production_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level, cluster_wt=scenario.cluster_wt)
-    location_discharge_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level, cluster_wt=scenario.cluster_wt)
-    location_consumption_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level, cluster_wt=scenario.cluster_wt)
-    location_purchase_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level, cluster_wt=scenario.cluster_wt)
-
-    network_production_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level)
-    network_discharge_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level)
-    network_consumption_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level)
-    network_purchase_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level)
-
-    process_capex_constraint(instance=instance, capex_dict=scenario.capex_dict,
-                             network_scale_level=scenario.network_scale_level)
-    process_fopex_constraint(instance=instance, fopex_dict=scenario.fopex_dict,
-                             network_scale_level=scenario.network_scale_level)
-    process_vopex_constraint(instance=instance, vopex_dict=scenario.vopex_dict,
-                             network_scale_level=scenario.network_scale_level)
-
-    process_land_constraint(instance=instance, land_dict=scenario.land_dict,
-                            network_scale_level=scenario.network_scale_level)
-    location_land_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level)
-    network_land_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level)
-
-    location_capex_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level)
-    location_fopex_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level)
-    location_vopex_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level)
-
-    network_capex_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level)
-    network_fopex_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level)
-    network_vopex_constraint(
-        instance=instance, network_scale_level=scenario.network_scale_level)
-
-    demand_constraint(instance=instance, demand_scale_level=scenario.demand_scale_level,
-                      scheduling_scale_level=scenario.scheduling_scale_level, demand=scenario.demand)
-
-    carbon_emission_location_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    carbon_emission_network_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-
-
-    if len(scenario.location_set) > 1:
-        transport_export_constraint(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
-                                    transport_avail_dict=scenario.transport_avail_dict)
-        transport_import_constraint(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
-                                    transport_avail_dict=scenario.transport_avail_dict)
-        transport_exp_UB_constraint(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
-                                    trans_max=scenario.trans_max, transport_avail_dict=scenario.transport_avail_dict)
-        transport_imp_UB_constraint(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
-                                    trans_max=scenario.trans_max, transport_avail_dict=scenario.transport_avail_dict)
-        transport_balance_constraint(
-            instance=instance, scheduling_scale_level=scenario.scheduling_scale_level)
-
-        transport_exp_cost_constraint(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
-                                      trans_cost=scenario.trans_cost, distance_dict=scenario.distance_dict)
-        transport_imp_cost_constraint(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
-                                      trans_cost=scenario.trans_cost, distance_dict=scenario.distance_dict)
-        transport_cost_constraint(
-            instance=instance, scheduling_scale_level=scenario.scheduling_scale_level)
-        transport_cost_network_constraint(
-            instance=instance, network_scale_level=scenario.network_scale_level)
-
-    
-
-    cost_objective(instance=instance,
-                   network_scale_level=scenario.network_scale_level)
-
-    return instance
diff --git a/src/energiapy/formulation/mplp.py b/src/energiapy/formulation/mplp.py
deleted file mode 100644
index 33fcabf6..00000000
--- a/src/energiapy/formulation/mplp.py
+++ /dev/null
@@ -1,108 +0,0 @@
-"""Formulates a multiscale mixed integer linear programming (MILP) model from Scenario  
-"""
-
-__author__ = "Rahul Kakodkar"
-__copyright__ = "Copyright 2022, Multi-parametric Optimization & Control Lab"
-__credits__ = ["Rahul Kakodkar", "Efstratios N. Pistikopoulos"]
-__license__ = "Open"
-__version__ = "0.0.1"
-__maintainer__ = "Rahul Kakodkar"
-__email__ = "cacodcar@tamu.edu"
-__status__ = "Production"
-
-from ..components.scenario import Scenario
-from ..model.sets import generate_sets
-from ..model.variables import *
-from ..model.constraints import *
-from ..model.objectives import uncertainty_cost_objective
-from pyomo.environ import ConcreteModel, Suffix
-
-    
-def formulate_mplp(scenario: Scenario, relax: dict = None, penalty = float) -> ConcreteModel:
-    """formulates a multi-scale mixed integer linear programming formulation of the scenario
-    
-    Args:
-        scenario (Scenario): scenario under consideration
-
-    Returns:
-        ConcreteModel: pyomo model instance with sets, variables, constraints, objectives generated
-    """
-    
-    instance = ConcreteModel()
-    
-    generate_sets(instance= instance, location_set= scenario.location_set, transport_set= scenario.transport_set, scales= scenario.scales, \
-        process_set= scenario.process_set, resource_set= scenario.resource_set, material_set= scenario.material_set, \
-            source_set= scenario.source_locations, sink_set= scenario.sink_locations)
-
-    generate_scheduling_vars(instance = instance, scale_level= scenario.scheduling_scale_level)
-    generate_network_vars(instance = instance, scale_level= scenario.network_scale_level)
-    generate_uncertainty_vars(instance= instance, scale_level= scenario.scheduling_scale_level)
-    if len(instance.locations) > 1:
-        generate_transport_vars(instance= instance, scale_level= scenario.scheduling_scale_level) 
-    
-    inventory_balance_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level,\
-        conversion= scenario.conversion)
-    uncertain_nameplate_production_constraint(instance= instance, network_scale_level= \
-        scenario.network_scale_level, scheduling_scale_level= scenario.scheduling_scale_level)
-    nameplate_inventory_constraint(instance= instance, loc_res_dict= scenario.loc_res_dict, network_scale_level= scenario.network_scale_level,\
-        scheduling_scale_level= scenario.scheduling_scale_level)
-    resource_consumption_constraint(instance= instance, loc_res_dict= scenario.loc_res_dict, cons_max= scenario.cons_max, scheduling_scale_level= scenario.scheduling_scale_level)
-    uncertain_resource_purchase_constraint(instance= instance, price= scenario.price, \
-        loc_res_dict= scenario.loc_res_dict, scheduling_scale_level= scenario.scheduling_scale_level, \
-            expenditure_scale_level= scenario.expenditure_scale_level)
-    # resource_discharge_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level)
-
-    # production_facility_fix_constraint(instance= instance, production_binaries = relax['X_P'],prod_max= scenario.prod_max, loc_pro_dict= scenario.loc_pro_dict, network_scale_level= scenario.network_scale_level)
-    # storage_facility_fix_constraint(instance= instance, storage_binaries = relax['X_S'], store_max= scenario.store_max, loc_res_dict= scenario.loc_res_dict, network_scale_level= scenario.network_scale_level)
-    delta_cap_location_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    delta_cap_network_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-        
-    
-    location_production_constraint(instance= instance, network_scale_level= scenario.network_scale_level, cluster_wt = scenario.cluster_wt)
-    location_discharge_constraint(instance= instance, network_scale_level= scenario.network_scale_level, cluster_wt = scenario.cluster_wt)
-    location_consumption_constraint(instance= instance, network_scale_level= scenario.network_scale_level, cluster_wt = scenario.cluster_wt)
-    location_purchase_constraint(instance= instance, network_scale_level= scenario.network_scale_level, cluster_wt = scenario.cluster_wt)
-
-    network_production_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    network_discharge_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    network_consumption_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    network_purchase_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-
-
-    process_capex_constraint(instance= instance, capex_dict= scenario.capex_dict, network_scale_level= scenario.network_scale_level)
-    process_fopex_constraint(instance= instance, fopex_dict= scenario.fopex_dict, network_scale_level= scenario.network_scale_level)
-    process_vopex_constraint(instance= instance, vopex_dict= scenario.vopex_dict, network_scale_level= scenario.network_scale_level)
-    
-    process_land_constraint(instance= instance, land_dict= scenario.land_dict, network_scale_level= scenario.network_scale_level)
-    location_land_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    network_land_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    
-
-    location_capex_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    location_fopex_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    location_vopex_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    
-    network_capex_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    network_fopex_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    network_vopex_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    
-    demand_constraint(instance= instance, demand_scale_level= scenario.demand_scale_level, scheduling_scale_level= scenario.scheduling_scale_level, demand= scenario.demand)
-    
-    if len(scenario.location_set) > 1:
-        transport_export_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level, transport_avail_dict= scenario.transport_avail_dict)
-        transport_import_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level, transport_avail_dict= scenario.transport_avail_dict)
-        transport_exp_UB_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level, trans_max= scenario.trans_max, transport_avail_dict= scenario.transport_avail_dict)  
-        transport_imp_UB_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level, trans_max= scenario.trans_max, transport_avail_dict= scenario.transport_avail_dict)  
-        transport_balance_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level)
-        
-        transport_exp_cost_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level, trans_cost= scenario.trans_cost, distance_dict= scenario.distance_dict)  
-        transport_imp_cost_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level, trans_cost= scenario.trans_cost, distance_dict= scenario.distance_dict)  
-        transport_cost_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level)
-        transport_cost_network_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-        
-        
-    instance.dual = Suffix(direction=Suffix.IMPORT)
-
-    uncertainty_cost_objective(instance= instance, penalty = penalty, network_scale_level= scenario.network_scale_level)
-    
-    return instance
diff --git a/src/energiapy/formulation/mpmilp.py b/src/energiapy/formulation/mpmilp.py
deleted file mode 100644
index f37b324d..00000000
--- a/src/energiapy/formulation/mpmilp.py
+++ /dev/null
@@ -1,104 +0,0 @@
-"""Formulates a multiparameteric mixed integer linear programming model (mpMILP) from Scenario  
-"""
-
-__author__ = "Rahul Kakodkar"
-__copyright__ = "Copyright 2022, Multi-parametric Optimization & Control Lab"
-__credits__ = ["Rahul Kakodkar", "Efstratios N. Pistikopoulos"]
-__license__ = "Open"
-__version__ = "0.0.1"
-__maintainer__ = "Rahul Kakodkar"
-__email__ = "cacodcar@tamu.edu"
-__status__ = "Production"
-
-from ..components.scenario import Scenario
-from ..model.sets import generate_sets
-from ..model.variables import *
-from ..model.constraints import *
-from ..model.objectives import uncertainty_cost_objective
-from pyomo.environ import ConcreteModel, Suffix
-    
-      
-def formulate_mpmilp(scenario: Scenario, penalty:float) -> ConcreteModel:
-    """formulates a multi-scale multi-parametric mixed integer linear programming formulation of the scenario
-    
-    Args:
-        scenario (Scenario): scenario under consideration
-
-    Returns:
-        ConcreteModel: pyomo model instance with sets, variables, constraints, objectives generated
-    """
-    instance = ConcreteModel()
-    generate_sets(instance= instance, location_set= scenario.location_set, transport_set= scenario.transport_set, scales= scenario.scales, \
-        process_set= scenario.process_set, resource_set= scenario.resource_set, material_set= scenario.material_set, \
-            source_set= scenario.source_locations, sink_set= scenario.sink_locations)
-    
-    generate_scheduling_vars(instance = instance, scale_level= scenario.scheduling_scale_level)
-    generate_network_vars(instance = instance, scale_level= scenario.network_scale_level)
-    generate_network_binary_vars(instance = instance)
-    generate_transport_vars(instance= instance, scale_level= scenario.scheduling_scale_level)
-    generate_uncertainty_vars(instance= instance, scale_level= scenario.scheduling_scale_level)
-    
-    inventory_balance_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level,\
-        conversion= scenario.conversion)
-    
-    uncertain_nameplate_production_constraint(instance= instance, network_scale_level= scenario.network_scale_level, scheduling_scale_level= scenario.scheduling_scale_level)
-    nameplate_inventory_constraint(instance= instance, loc_res_dict= scenario.loc_res_dict, network_scale_level= scenario.network_scale_level,\
-        scheduling_scale_level= scenario.scheduling_scale_level)
-    
-    resource_consumption_constraint(instance= instance, loc_res_dict= scenario.loc_res_dict, cons_max= scenario.cons_max, scheduling_scale_level= scenario.scheduling_scale_level)
-    uncertain_resource_purchase_constraint(instance= instance, price= scenario.price, loc_res_dict= scenario.loc_res_dict, \
-        scheduling_scale_level= scenario.scheduling_scale_level, expenditure_scale_level= scenario.expenditure_scale_level)
-    # resource_discharge_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level)
-    
-    production_facility_constraint(instance= instance, prod_max= scenario.prod_max, loc_pro_dict= scenario.loc_pro_dict, network_scale_level= scenario.network_scale_level)
-    storage_facility_constraint(instance= instance, store_max= scenario.store_max, loc_res_dict= scenario.loc_res_dict, network_scale_level= scenario.network_scale_level)
-    
-    # min_production_facility_constraint(instance= instance, prod_min= scenario.prod_min, loc_pro_dict= scenario.loc_pro_dict, network_scale_level= scenario.network_scale_level)
-    # min_storage_facility_constraint(instance= instance, store_min= scenario.store_min, loc_res_dict= scenario.loc_res_dict, network_scale_level= scenario.network_scale_level)
-        
-                
-    delta_cap_location_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    delta_cap_network_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-        
-    
-    location_production_constraint(instance= instance, network_scale_level= scenario.network_scale_level, cluster_wt = scenario.cluster_wt)
-    location_discharge_constraint(instance= instance, network_scale_level= scenario.network_scale_level, cluster_wt = scenario.cluster_wt)
-    location_consumption_constraint(instance= instance, network_scale_level= scenario.network_scale_level, cluster_wt = scenario.cluster_wt)
-    location_purchase_constraint(instance= instance, network_scale_level= scenario.network_scale_level, cluster_wt = scenario.cluster_wt)
-    
-
-    network_production_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    network_discharge_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    network_consumption_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    network_purchase_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    
- 
-    process_capex_constraint(instance= instance, capex_dict= scenario.capex_dict, network_scale_level= scenario.network_scale_level)
-    process_fopex_constraint(instance= instance, fopex_dict= scenario.fopex_dict, network_scale_level= scenario.network_scale_level)
-    process_vopex_constraint(instance= instance, vopex_dict= scenario.vopex_dict, network_scale_level= scenario.network_scale_level)
-
-    location_capex_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    location_fopex_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    location_vopex_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    
-    network_capex_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    network_fopex_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    network_vopex_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-    
-    demand_constraint(instance= instance, demand_scale_level= scenario.demand_scale_level, scheduling_scale_level= scenario.scheduling_scale_level, demand= scenario.demand)
-    
-    transport_export_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level, transport_avail_dict= scenario.transport_avail_dict)
-    transport_import_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level, transport_avail_dict= scenario.transport_avail_dict)
-    transport_exp_UB_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level, trans_max= scenario.trans_max, transport_avail_dict= scenario.transport_avail_dict)  
-    transport_imp_UB_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level, trans_max= scenario.trans_max, transport_avail_dict= scenario.transport_avail_dict)  
-    transport_balance_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level)
-    
-    transport_exp_cost_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level, trans_cost= scenario.trans_cost, distance_dict= scenario.distance_dict)  
-    transport_imp_cost_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level, trans_cost= scenario.trans_cost, distance_dict= scenario.distance_dict)  
-    transport_cost_constraint(instance= instance, scheduling_scale_level= scenario.scheduling_scale_level)
-    transport_cost_network_constraint(instance= instance, network_scale_level= scenario.network_scale_level)
-
-    uncertainty_cost_objective(instance= instance, penalty = penalty, network_scale_level= scenario.network_scale_level)
-    
-    return instance
-       
\ No newline at end of file
diff --git a/src/energiapy/graph/__init__.py b/src/energiapy/graph/__init__.py
index 46d7931a..e69de29b 100644
--- a/src/energiapy/graph/__init__.py
+++ b/src/energiapy/graph/__init__.py
@@ -1,11 +0,0 @@
-"""energiapy.graph init file
-"""
-
-__author__ = "Rahul Kakodkar"
-__copyright__ = "Copyright 2022, Multi-parametric Optimization & Control Lab"
-__credits__ = ["Rahul Kakodkar", "Efstratios N. Pistikopoulos"]
-__license__ = "Open"
-__version__ = "0.0.1"
-__maintainer__ = "Rahul Kakodkar"
-__email__ = "cacodcar@tamu.edu"
-__status__ = "Production"
diff --git a/src/energiapy/model/__init__.py b/src/energiapy/model/__init__.py
index b257cd32..e69de29b 100644
--- a/src/energiapy/model/__init__.py
+++ b/src/energiapy/model/__init__.py
@@ -1,11 +0,0 @@
-"""energiapy.model init file
-"""
-
-__author__ = "Rahul Kakodkar"
-__copyright__ = "Copyright 2022, Multi-parametric Optimization & Control Lab"
-__credits__ = ["Rahul Kakodkar", "Efstratios N. Pistikopoulos"]
-__license__ = "Open"
-__version__ = "0.0.1"
-__maintainer__ = "Rahul Kakodkar"
-__email__ = "cacodcar@tamu.edu"
-__status__ = "Production"
diff --git a/src/energiapy/model/constraints/cost.py b/src/energiapy/model/constraints/cost.py
index fd76ae36..77c8c6a5 100644
--- a/src/energiapy/model/constraints/cost.py
+++ b/src/energiapy/model/constraints/cost.py
@@ -30,21 +30,43 @@ class Costdynamics(Enum):
     solar = auto()
 
 
-def transport_exp_cost_constraint(instance: ConcreteModel, scheduling_scale_level: int = 0, trans_cost: dict = {}, distance_dict: dict = {}) -> Constraint:
-    scales = scale_list(instance=instance,
-                        scale_levels=scheduling_scale_level+1)
-
-    def transport_exp_cost_rule(instance, source, sink, resource, transport, *scale_list):
-        return instance.Trans_exp_cost[source, sink, resource, transport, scale_list[:scheduling_scale_level+1]] == \
-            trans_cost[transport]*distance_dict[(source, sink)]*instance.Trans_exp[source,
-                                                                                   sink, resource, transport, scale_list[:scheduling_scale_level+1]]
-    instance.transport_exp_cost_constraint = Constraint(instance.sources, instance.sinks, instance.resources_trans,
-                                                        instance.transports, *scales, rule=transport_exp_cost_rule, doc='import of resource from sink to source')
-    constraint_latex_render(transport_exp_cost_rule)
-    return instance.transport_exp_cost_constraint
+# def constraint_transport_exp_cost(instance: ConcreteModel, scheduling_scale_level: int = 0, trans_cost: dict = {}, distance_dict: dict = {}) -> Constraint:
+#     """Calculates the expenditure on exporting resource throught transport
+
+#     Args:
+#         instance (ConcreteModel): pyomo instance
+#         scheduling_scale_level (int, optional): scheduling scale level. Defaults to 0.
+#         trans_cost (dict, optional): dictionary with transport costs. Defaults to {}.
+#         distance_dict (dict, optional): dictionary with distances. Defaults to {}.
+
+#     Returns:
+#         Constraint: transport_exp_cost
+#     """
+#     scales = scale_list(instance=instance,
+#                         scale_levels=scheduling_scale_level+1)
+
+#     def transport_exp_cost_rule(instance, source, sink, resource, transport, *scale_list):
+#         return instance.Trans_exp_cost[source, sink, resource, transport, scale_list[:scheduling_scale_level+1]] == \
+#             trans_cost[transport]*distance_dict[(source, sink)]*instance.Trans_exp[source,
+#                                                                                    sink, resource, transport, scale_list[:scheduling_scale_level+1]]
+#     instance.constraint_transport_exp_cost = Constraint(instance.sources, instance.sinks, instance.resources_trans,
+#                                                         instance.transports, *scales, rule=transport_exp_cost_rule, doc='import of resource from sink to source')
+#     constraint_latex_render(transport_exp_cost_rule)
+#     return instance.constraint_transport_exp_cost
+
+
+def constraint_transport_imp_cost(instance: ConcreteModel, scheduling_scale_level: int = 0, trans_cost: dict = {}, distance_dict: dict = {}) -> Constraint:    
+    """Calculates the expenditure on importing resource throught transport
 
+    Args:
+        instance (ConcreteModel): pyomo instance
+        scheduling_scale_level (int, optional): scheduling scale level. Defaults to 0.
+        trans_cost (dict, optional): dictionary with transport costs. Defaults to {}.
+        distance_dict (dict, optional): dictionary with distances. Defaults to {}.
 
-def transport_imp_cost_constraint(instance: ConcreteModel, scheduling_scale_level: int = 0, trans_cost: dict = {}, distance_dict: dict = {}) -> Constraint:
+    Returns:
+        Constraint: transport_imp_cost
+    """
     scales = scale_list(instance=instance,
                         scale_levels=scheduling_scale_level+1)
 
@@ -52,55 +74,71 @@ def transport_imp_cost_rule(instance, sink, source, resource, transport, *scale_
         return instance.Trans_imp_cost[sink, source, resource, transport, scale_list[:scheduling_scale_level+1]] == \
             trans_cost[transport]*distance_dict[(source, sink)]*instance.Trans_imp[sink,
                                                                                    source, resource, transport, scale_list[:scheduling_scale_level+1]]
-    instance.transport_imp_cost_constraint = Constraint(instance.sinks, instance.sources, instance.resources_trans,
+    instance.constraint_transport_imp_cost = Constraint(instance.sinks, instance.sources, instance.resources_trans,
                                                         instance.transports, *scales, rule=transport_imp_cost_rule, doc='import of resource from sink to source')
     constraint_latex_render(transport_imp_cost_rule)
-    return instance.transport_imp_cost_constraint
+    return instance.constraint_transport_imp_cost
 
 # *-------------------------Transport costing constraints--------------------------
 
 
-def transport_cost_constraint(instance: ConcreteModel, scheduling_scale_level: int = 0):
+def constraint_transport_cost(instance: ConcreteModel, scheduling_scale_level: int = 0) -> Constraint:
+    """Total cost for transport mode for all resources
+
+    Args:
+        instance (ConcreteModel): pyomo instance
+        scheduling_scale_level (int, optional): scale for scheduling decisions. Defaults to 0.
+
+    Returns:
+        Constraint: transport_cost
+    """
     scales = scale_list(instance=instance,
                         scale_levels=scheduling_scale_level+1)
 
     def transport_cost_rule(instance, transport, *scale_list):
         return instance.Trans_cost[transport, scale_list[:scheduling_scale_level+1]] == \
-            sum(instance.Trans_imp_cost[sink, source, resource, transport, scale_list[:scheduling_scale_level+1]] +
-                instance.Trans_exp_cost[source, sink, resource,
-                                        transport, scale_list[:scheduling_scale_level+1]]
+            sum(instance.Trans_imp_cost[sink, source, resource, transport, scale_list[:scheduling_scale_level+1]] 
                 for sink, source, resource in product(instance.sinks, instance.sources, instance.resources_trans))
-    instance.transport_cost_constraint = Constraint(
+    instance.constraint_transport_cost = Constraint(
         instance.transports, *scales, rule=transport_cost_rule, doc='total transport cost')
     constraint_latex_render(transport_cost_rule)
-    return instance.transport_cost_constraint
+    return instance.constraint_transport_cost
+
 
+def constraint_transport_cost_network(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+    """Constraint for transport of all resources at network level
 
-def transport_cost_network_constraint(instance: ConcreteModel, network_scale_level: int = 0):
+    Args:
+        instance (ConcreteModel): pyomo instance
+        network_scale_level (int, optional): scale for network decisions. Defaults to 0.
+
+    Returns:
+        Constraint: transport_cost_network
+    """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
     scale_iter = scale_tuple(
         instance=instance, scale_levels=instance.scales.__len__())
 
     def transport_cost_network_rule(instance, transport, *scale_list):
         return instance.Trans_cost_network[transport, scale_list] == sum(instance.Trans_cost[transport, scale_] for scale_ in scale_iter)
-    instance.transport_cost_network_constraint = Constraint(
+    instance.constraint_transport_cost_network = Constraint(
         instance.transports, *scales, rule=transport_cost_network_rule, doc='total transport cost across scale')
     constraint_latex_render(transport_cost_network_rule)
-    return instance.transport_cost_network_constraint
+    return instance.constraint_transport_cost_network
 
 
-def process_capex_constraint(instance: ConcreteModel, capex_dict: dict, network_scale_level: int = 0, annualization_factor: float = 1,\
+def constraint_process_capex(instance: ConcreteModel, capex_dict: dict, network_scale_level: int = 0, annualization_factor: float = 1,\
     cost_dynamics: Costdynamics = Costdynamics.constant) -> Constraint:
     """Capital expenditure for each process at location in network
 
     Args:
         instance (ConcreteModel): pyomo instance
-        capex_dict (dict): capex at location #TODO
+        capex_dict (dict): capex at location 
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
         annualization_factor (float, optional): Annual depreciation of asset. Defaults to 1.
 
     Returns:
-        Constraint: process_capex_constraint
+        Constraint: process_capex
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
     
@@ -109,24 +147,24 @@ def process_capex_rule(instance, location, process, *scale_list):
             return instance.Capex_process[location, process, scale_list] == annualization_factor*capex_dict[process]*instance.Cap_P[location, process, scale_list]
         else:
             return instance.Capex_process[location, process, scale_list] == 0
-    instance.process_capex_constraint = Constraint(
+    instance.constraint_process_capex = Constraint(
         instance.locations, instance.processes, *scales, rule=process_capex_rule, doc='total purchase from network')
     
     constraint_latex_render(process_capex_rule)
-    return instance.process_capex_constraint
+    return instance.constraint_process_capex
 
-def process_incidental_constraint(instance: ConcreteModel, incidental_dict: dict, network_scale_level: int = 0, annualization_factor: float = 1,\
+def constraint_process_incidental(instance: ConcreteModel, incidental_dict: dict, network_scale_level: int = 0, annualization_factor: float = 1,\
     cost_dynamics: Costdynamics = Costdynamics.constant) -> Constraint:
     """Capital expenditure for each process at location in network
 
     Args:
         instance (ConcreteModel): pyomo instance
-        capex_dict (dict): capex at location #TODO
+        capex_dict (dict): capex at location
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
         annualization_factor (float, optional): Annual depreciation of asset. Defaults to 1.
 
     Returns:
-        Constraint: process_capex_constraint
+        Constraint: process_capex
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
     
@@ -135,23 +173,23 @@ def process_incidental_rule(instance, location, process, *scale_list):
             return instance.Incidental_process[location, process, scale_list] ==incidental_dict[process]
         else:
             return instance.Incidental_process[location, process, scale_list] == 0
-    instance.process_incidental_constraint = Constraint(
+    instance.constraint_process_incidental = Constraint(
         instance.locations, instance.processes, *scales, rule=process_incidental_rule, doc='total incidental costs from processes')
     
     constraint_latex_render(process_incidental_rule)
-    return instance.process_incidental_constraint
+    return instance.constraint_process_incidental
 
 
-def process_fopex_constraint(instance: ConcreteModel, fopex_dict: dict, network_scale_level: int = 0, annualization_factor: float = 1) -> Constraint:
+def constraint_process_fopex(instance: ConcreteModel, fopex_dict: dict, network_scale_level: int = 0, annualization_factor: float = 1) -> Constraint:
     """Fixed operational expenditure for each process at location in network
     Args:
         instance (ConcreteModel): pyomo instance
-        fopex_dict (dict): fixed opex at location #TODO
+        fopex_dict (dict): fixed opex at location
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
         annualization_factor (float, optional): Annual depreciation of asset. Defaults to 1.
 
     Returns:
-        Constraint: process_fopex_constraint
+        Constraint: process_fopex
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
@@ -160,13 +198,13 @@ def process_fopex_rule(instance, location, process, *scale_list):
             return instance.Fopex_process[location, process, scale_list] == annualization_factor*fopex_dict[process]*instance.Cap_P[location, process, scale_list]
         else:
             return instance.Fopex_process[location, process, scale_list] == 0 
-    instance.process_fopex_constraint = Constraint(
+    instance.constraint_process_fopex = Constraint(
         instance.locations, instance.processes, *scales, rule=process_fopex_rule, doc='total purchase from network')
     constraint_latex_render(process_fopex_rule)
-    return instance.process_fopex_constraint
+    return instance.constraint_process_fopex
 
 
-def process_vopex_constraint(instance: ConcreteModel, vopex_dict: dict, network_scale_level: int = 0, annualization_factor: float = 1) -> Constraint:
+def constraint_process_vopex(instance: ConcreteModel, vopex_dict: dict, network_scale_level: int = 0, annualization_factor: float = 1) -> Constraint:
     """Fixed operational expenditure for each process at location in network
 
     Args:
@@ -174,7 +212,7 @@ def process_vopex_constraint(instance: ConcreteModel, vopex_dict: dict, network_
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: process_vopex_constraint
+        Constraint: process_vopex
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
@@ -183,15 +221,15 @@ def process_vopex_rule(instance, location, process, *scale_list):
             return instance.Vopex_process[location, process, scale_list] == annualization_factor*vopex_dict[process]*instance.P_location[location, process, scale_list]
         else:
             return instance.Vopex_process[location, process, scale_list] == 0
-    instance.process_vopex_constraint = Constraint(
+    instance.constraint_process_vopex = Constraint(
         instance.locations, instance.processes, *scales, rule=process_vopex_rule, doc='total purchase from network')
     constraint_latex_render(process_vopex_rule)
-    return instance.process_vopex_constraint
+    return instance.constraint_process_vopex
 
 
 # *-------------------------Location costing constraints--------------------------------------
 
-def location_capex_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+def constraint_location_capex(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
     """Capital expenditure for each process at location in network
 
     Args:
@@ -199,18 +237,18 @@ def location_capex_constraint(instance: ConcreteModel, network_scale_level: int
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: location_capex_constraint
+        Constraint: location_capex
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def location_capex_rule(instance, location, *scale_list):
         return instance.Capex_location[location, scale_list] == sum(instance.Capex_process[location, process_, scale_list] for process_ in instance.processes)
-    instance.location_capex_constraint = Constraint(
+    instance.constraint_location_capex = Constraint(
         instance.locations, *scales, rule=location_capex_rule, doc='total purchase from network')
     constraint_latex_render(location_capex_rule)
-    return instance.location_capex_constraint
+    return instance.constraint_location_capex
 
-def location_incidental_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+def constraint_location_incidental(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
     """Capital expenditure for each process at location in network
 
     Args:
@@ -218,18 +256,18 @@ def location_incidental_constraint(instance: ConcreteModel, network_scale_level:
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: location_incidental_constraint
+        Constraint: location_incidental
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def location_incidental_rule(instance, location, *scale_list):
         return instance.Incidental_location[location, scale_list] == sum(instance.Incidental_process[location, process_, scale_list] for process_ in instance.processes)
-    instance.location_incidental_constraint = Constraint(
+    instance.constraint_location_incidental = Constraint(
         instance.locations, *scales, rule=location_incidental_rule, doc='total purchase from network')
     constraint_latex_render(location_incidental_rule)
-    return instance.location_incidental_constraint
+    return instance.constraint_location_incidental
 
-def location_fopex_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+def constraint_location_fopex(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
     """Fixed operational expenditure for each process at location in network
 
     Args:
@@ -237,19 +275,19 @@ def location_fopex_constraint(instance: ConcreteModel, network_scale_level: int
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: location_fopex_constraint
+        Constraint: location_fopex
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def location_fopex_rule(instance, location, *scale_list):
         return instance.Fopex_location[location, scale_list] == sum(instance.Fopex_process[location, process_, scale_list] for process_ in instance.processes)
-    instance.location_fopex_constraint = Constraint(
+    instance.constraint_location_fopex = Constraint(
         instance.locations, *scales, rule=location_fopex_rule, doc='total purchase from network')
     constraint_latex_render(location_fopex_rule)
-    return instance.location_fopex_constraint
+    return instance.constraint_location_fopex
 
 
-def location_vopex_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+def constraint_location_vopex(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
     """Fixed operational expenditure for each process at location in network
 
     Args:
@@ -257,21 +295,21 @@ def location_vopex_constraint(instance: ConcreteModel, network_scale_level: int
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: location_vopex_constraint
+        Constraint: location_vopex
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def location_vopex_rule(instance, location, *scale_list):
         return instance.Vopex_location[location, scale_list] == sum(instance.Vopex_process[location, process_, scale_list] for process_ in instance.processes)
-    instance.location_vopex_constraint = Constraint(
+    instance.constraint_location_vopex = Constraint(
         instance.locations, *scales, rule=location_vopex_rule, doc='total purchase from network')
     constraint_latex_render(location_vopex_rule)
-    return instance.location_vopex_constraint
+    return instance.constraint_location_vopex
 
 
 # *-------------------------Network costing constraints--------------------------------------
 
-def network_capex_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+def constraint_network_capex(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
     """Capital expenditure for each process at location in network
 
     Args:
@@ -279,19 +317,19 @@ def network_capex_constraint(instance: ConcreteModel, network_scale_level: int =
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: network_capex_constraint
+        Constraint: network_capex
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def network_capex_rule(instance, *scale_list):
         return instance.Capex_network[scale_list] == sum(instance.Capex_location[location_, scale_list] for location_ in instance.locations)
-    instance.network_capex_constraint = Constraint(
+    instance.constraint_network_capex = Constraint(
         *scales, rule=network_capex_rule, doc='total purchase from network')
     constraint_latex_render(network_capex_rule)
-    return instance.network_capex_constraint
+    return instance.constraint_network_capex
 
 
-def network_incidental_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+def constraint_network_incidental(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
     """Capital expenditure for each process at location in network
 
     Args:
@@ -299,19 +337,19 @@ def network_incidental_constraint(instance: ConcreteModel, network_scale_level:
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: network_incidental_constraint
+        Constraint: network_incidental
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def network_incidental_rule(instance, *scale_list):
         return instance.Incidental_network[scale_list] == sum(instance.Incidental_location[location_, scale_list] for location_ in instance.locations)
-    instance.network_incidental_constraint = Constraint(
+    instance.constraint_network_incidental = Constraint(
         *scales, rule=network_incidental_rule, doc='total purchase from network')
     constraint_latex_render(network_incidental_rule)
-    return instance.network_incidental_constraint
+    return instance.constraint_network_incidental
 
 
-def network_vopex_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+def constraint_network_vopex(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
     """Variable operational expenditure for each process at location in network
 
     Args:
@@ -319,19 +357,19 @@ def network_vopex_constraint(instance: ConcreteModel, network_scale_level: int =
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: network_vopex_constraint
+        Constraint: network_vopex
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def network_vopex_rule(instance, *scale_list):
         return instance.Vopex_network[scale_list] == sum(instance.Vopex_location[location_, scale_list] for location_ in instance.locations)
-    instance.network_vopex_constraint = Constraint(
+    instance.constraint_network_vopex = Constraint(
         *scales, rule=network_vopex_rule, doc='total purchase from network')
     constraint_latex_render(network_vopex_rule)
-    return instance.network_vopex_constraint
+    return instance.constraint_network_vopex
 
 
-def network_fopex_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+def constraint_network_fopex(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
     """Fixed operational for each process at location in network
 
     Args:
@@ -339,13 +377,77 @@ def network_fopex_constraint(instance: ConcreteModel, network_scale_level: int =
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: network_fopex_constraint
+        Constraint: network_fopex
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def network_fopex_rule(instance, *scale_list):
         return instance.Fopex_network[scale_list] == sum(instance.Fopex_location[location_, scale_list] for location_ in instance.locations)
-    instance.network_fopex_constraint = Constraint(
+    instance.constraint_network_fopex = Constraint(
         *scales, rule=network_fopex_rule, doc='total purchase from network')
     constraint_latex_render(network_fopex_rule)
-    return instance.network_fopex_constraint
\ No newline at end of file
+    return instance.constraint_network_fopex
+
+
+def constraint_process_land_cost(instance: ConcreteModel, land_dict: dict, land_cost_dict:dict, network_scale_level: int = 0) -> Constraint:
+    """Land cost for each process at location in network
+
+    Args:
+        instance (ConcreteModel): pyomo instance
+        land_dict (dict): land required at location
+        land_cost_dict (dict): land cost at location
+        network_scale_level (int, optional): scale of network decisions. Defaults to 0.
+
+    Returns:
+        Constraint: process_land
+    """
+    scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
+
+    def process_land_cost_rule(instance, location, process, *scale_list):
+        return instance.Land_cost_process[location, process, scale_list] == land_cost_dict[location]*land_dict[process]*instance.Cap_P[location, process, scale_list]
+    instance.constraint_process_land_cost = Constraint(
+        instance.locations, instance.processes, *scales, rule=process_land_cost_rule, doc='land cost for process at location')
+    constraint_latex_render(process_land_cost_rule)
+    return instance.constraint_process_land_cost
+
+
+
+def constraint_location_land_cost(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+    """Land cost each location in network
+
+    Args:
+        instance (ConcreteModel): pyomo instance
+        network_scale_level (int, optional): scale of network decisions. Defaults to 0.
+
+    Returns:
+        Constraint: location_land_cost
+    """
+    scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
+
+    def location_land_cost_rule(instance, location, *scale_list):
+        return instance.Land_cost_location[location, scale_list] == sum(instance.Land_cost_process[location, process_, scale_list] for process_ in instance.processes)
+    instance.constraint_location_land_cost = Constraint(
+        instance.locations, *scales, rule=location_land_cost_rule, doc='land cost at location')
+    constraint_latex_render(location_land_cost_rule)
+    return instance.constraint_location_land_cost
+
+
+
+def constraint_network_land_cost(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+    """Land cost by network
+
+    Args:
+        instance (ConcreteModel): pyomo instance
+        network_scale_level (int, optional): scale of network decisions. Defaults to 0.
+
+    Returns:
+        Constraint: network_land_cost
+    """
+    scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
+
+    def network_land_cost_rule(instance, *scale_list):
+        return instance.Land_cost_network[scale_list] == sum(instance.Land_cost_location[location_, scale_list] for location_ in instance.locations)
+    instance.constraint_network_land_cost = Constraint(
+        *scales, rule=network_land_cost_rule, doc='land cost for process')
+    constraint_latex_render(network_land_cost_rule)
+    return instance.constraint_network_land_cost
\ No newline at end of file
diff --git a/src/energiapy/model/constraints/emission.py b/src/energiapy/model/constraints/emission.py
index 6b31bb96..55e0fee7 100644
--- a/src/energiapy/model/constraints/emission.py
+++ b/src/energiapy/model/constraints/emission.py
@@ -22,7 +22,7 @@
 
 
 
-def carbon_emission_location_constraint(instance: ConcreteModel, network_scale_level:int=0) -> Constraint:
+def constraint_carbon_emission_location(instance: ConcreteModel, network_scale_level:int=0) -> Constraint:
     """Determines the total CO2_Vent at each location
 
     Args:
@@ -30,17 +30,17 @@ def carbon_emission_location_constraint(instance: ConcreteModel, network_scale_l
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: carbon_emission_location_constraint
+        Constraint: carbon_emission_location
     """
     scales = scale_list(instance= instance, scale_levels = network_scale_level+1)
     def carbon_emission_location_rule(instance, location, *scale_list):
         return instance.carbon_emission_location[location, scale_list] == instance.S_location[location, 'CO2_Vent', scale_list] 
-    instance.carbon_emission_location_constraint = Constraint(instance.locations, *scales, rule = carbon_emission_location_rule, doc = 'carbon_emission_location_process')
+    instance.constraint_carbon_emission_location = Constraint(instance.locations, *scales, rule = carbon_emission_location_rule, doc = 'carbon_emission_location_process')
     
     constraint_latex_render(carbon_emission_location_rule)
-    return instance.carbon_emission_location_constraint
+    return instance.constraint_carbon_emission_location
 
-def carbon_emission_constraint(instance: ConcreteModel, carbon_bound:float,  network_scale_level:int=0, carbon_reduction_percentage:float = 0.0) -> Constraint:
+def constraint_carbon_emission(instance: ConcreteModel, carbon_bound:float,  network_scale_level:int=0, carbon_reduction_percentage:float = 0.0) -> Constraint:
     """Determines the total network-wide CO2_Vent 
 
     Args:
@@ -50,48 +50,88 @@ def carbon_emission_constraint(instance: ConcreteModel, carbon_bound:float,  net
         carbon_reduction_percentage (float, optional): percentage reduction required. Defaults to 0.0.
 
     Returns:
-        Constraint: carbon_emission_constraint
+        Constraint: carbon_emission
     """
     scales = scale_list(instance= instance, scale_levels = network_scale_level+1)
     def carbon_emission_rule(instance, location, *scale_list):
         return instance.S_location[location, 'CO2_Vent', scale_list] <= (100.0 - carbon_reduction_percentage)*carbon_bound/100.0
-    instance.carbon_emission_constraint = Constraint(instance.locations, *scales, rule = carbon_emission_rule, doc = 'carbon_emission_process')
+    instance.constraint_carbon_emission = Constraint(instance.locations, *scales, rule = carbon_emission_rule, doc = 'carbon_emission_process')
     constraint_latex_render(carbon_emission_rule)
-    return instance.carbon_emission_constraint
+    return instance.constraint_carbon_emission
 
 
-def global_warming_potential_process_constraint(instance: ConcreteModel, process_gwp_dict: dict, network_scale_level:int=0):
+def constraint_global_warming_potential_process(instance: ConcreteModel, process_gwp_dict: dict, network_scale_level:int=0) -> Constraint:
+    """calculates global warming potential for each process
+
+    Args:
+        instance (ConcreteModel): pyomo model instance
+        process_gwp_dict (dict): _description_
+        network_scale_level (int, optional): scale for network decisions. Defaults to 0.
+
+    Returns:
+        Constraint: global_warming_potential_process
+    """
     scales = scale_list(instance= instance, scale_levels= network_scale_level+1)
     
     def global_warming_potential_process_rule(instance, location, process,  *scale_list):
         return instance.global_warming_potential_process[location, process, scale_list] == process_gwp_dict[location][process]*instance.Cap_P[location, process, scale_list]
-    instance.global_warming_potential_process_constraint = Constraint(instance.locations, instance.processes, *scales, rule = global_warming_potential_process_rule, doc= 'global warming potential for the each process' )
+    instance.constraint_global_warming_potential_process = Constraint(instance.locations, instance.processes, *scales, rule = global_warming_potential_process_rule, doc= 'global warming potential for the each process' )
     constraint_latex_render(global_warming_potential_process_rule)
-    return instance.global_warming_potential_process_constraint
+    return instance.constraint_global_warming_potential_process
+
+
+def constraint_global_warming_potential_material(instance: ConcreteModel, material_gwp_dict: dict, process_material_dict: dict, network_scale_level:int=0) -> Constraint:
+    """Calculates the global warming potential arising from the use of materials for processes
+
+    Args:
+        instance (ConcreteModel): pyomo model instance
+        material_gwp_dict (dict): GWP associated with each material 
+        process_material_dict (dict): Material consumed by each process
+        network_scale_level (int, optional): scale for network decisions. Defaults to 0.
 
-#TODO - Fix this, this needs some work
-def global_warming_potential_material_constraint(instance: ConcreteModel, material_gwp_dict: dict, process_material_dict: dict, network_scale_level:int=0):
+    Returns:
+        Constraint: global_warming_potential_material
+    """
     scales = scale_list(instance= instance, scale_levels= network_scale_level+1)
     
     def global_warming_potential_material_rule(instance, location, process,  *scale_list):
         return instance.global_warming_potential_material[location, process, scale_list] == \
             sum(process_material_dict[process][material]*material_gwp_dict[location][material] for material in process_material_dict[process].keys()) *\
                 instance.Cap_P[location, process, scale_list] 
-    instance.global_warming_potential_material_constraint = Constraint(instance.locations, instance.processes_materials, *scales, rule = global_warming_potential_material_rule, doc= 'global warming potential for the each material' )
+    instance.constraint_global_warming_potential_material = Constraint(instance.locations, instance.processes_materials, *scales, rule = global_warming_potential_material_rule, doc= 'global warming potential for the each material' )
     constraint_latex_render(global_warming_potential_material_rule)
-    return instance.global_warming_potential_material_constraint
+    return instance.constraint_global_warming_potential_material
+
+def constraint_global_warming_potential_resource(instance: ConcreteModel, resource_gwp_dict: dict, network_scale_level:int=0) -> Constraint:
+    """Calculates the global warming potential from each resource
+
+    Args:
+        instance (ConcreteModel): pyomo model instance
+        resource_gwp_dict (dict): GWP associated with each resource
+        network_scale_level (int, optional): scale for network decisions. Defaults to 0.
 
-def global_warming_potential_resource_constraint(instance: ConcreteModel, resource_gwp_dict: dict, network_scale_level:int=0):
+    Returns:
+        Constraint: global_warming_potential_resource
+    """
     scales = scale_list(instance= instance, scale_levels= network_scale_level+1)
     
     def global_warming_potential_resource_rule(instance, location, resource,  *scale_list):
         return instance.global_warming_potential_resource[location, resource, scale_list] == resource_gwp_dict[location][resource]*instance.C_location[location, resource, scale_list]
-    instance.global_warming_potential_resource_constraint = Constraint(instance.locations, instance.resources_purch, *scales, rule = global_warming_potential_resource_rule, doc= 'global warming potential for the each resource' )
+    instance.constraint_global_warming_potential_resource = Constraint(instance.locations, instance.resources_purch, *scales, rule = global_warming_potential_resource_rule, doc= 'global warming potential for the each resource' )
     constraint_latex_render(global_warming_potential_resource_rule)
-    return instance.global_warming_potential_resource_constraint
+    return instance.constraint_global_warming_potential_resource
 
 
-def global_warming_potential_location_constraint(instance: ConcreteModel, network_scale_level:int=0):
+def constraint_global_warming_potential_location(instance: ConcreteModel, network_scale_level:int=0) -> Constraint:
+    """Calculates the global warming potential at each location
+    
+    Args:
+        instance (ConcreteModel): pyomo model instance
+        network_scale_level (int, optional): scale for network decisions. Defaults to 0.
+
+    Returns:
+        Constraint: global_warming_potential_location
+    """
     scales = scale_list(instance= instance, scale_levels= network_scale_level+1)
     
     def global_warming_potential_location_rule(instance, location, *scale_list):
@@ -100,36 +140,48 @@ def global_warming_potential_location_rule(instance, location, *scale_list):
         gwp_material = sum(instance.global_warming_potential_material[location, material_, scale_list] for material_ in instance.processes_materials)
         
         return instance.global_warming_potential_location[location, scale_list] == gwp_process + gwp_resource + gwp_material
-    instance.global_warming_potential_location_constraint = Constraint(instance.locations, *scales, rule = global_warming_potential_location_rule, doc= 'global warming potential for the each location' )
+    instance.constraint_global_warming_potential_location = Constraint(instance.locations, *scales, rule = global_warming_potential_location_rule, doc= 'global warming potential for the each location' )
     constraint_latex_render(global_warming_potential_location_rule)
-    return instance.global_warming_potential_location_constraint
+    return instance.constraint_global_warming_potential_location
 
-def global_warming_potential_network_constraint(instance: ConcreteModel, network_scale_level:int=0):
+def constraint_global_warming_potential_network(instance: ConcreteModel, network_scale_level:int=0) -> Constraint:
+    """Global warming potential for the whole network
+
+    Args:
+        instance (ConcreteModel): pyomo model instance
+        network_scale_level (int, optional): scale for network decisions. Defaults to 0.
+
+    Returns:
+        Constraint: global_warming_potential_network
+    """
     scales = scale_list(instance= instance, scale_levels= network_scale_level+1)
     
     def global_warming_potential_network_rule(instance, *scale_list):
         return instance.global_warming_potential_network[scale_list] == \
             sum(instance.global_warming_potential_location[location_, scale_list] for location_ in instance.locations)
-    instance.global_warming_potential_network_constraint = Constraint(*scales, rule = global_warming_potential_network_rule, doc= 'global warming potential for the whole network' )
+    instance.constraint_global_warming_potential_network = Constraint(*scales, rule = global_warming_potential_network_rule, doc= 'global warming potential for the whole network' )
     constraint_latex_render(global_warming_potential_network_rule)
-    return instance.global_warming_potential_network_constraint
+    return instance.constraint_global_warming_potential_network
+
+def constraint_global_warming_potential_network_reduction(instance: ConcreteModel, network_scale_level:int=0, gwp_reduction_pct:float= 0, gwp:float = 0) -> Constraint:
+    """Required reduction in global warming potential at network level
+
+    Args:
+        instance (ConcreteModel): pyomo model instance
+        network_scale_level (int, optional): scale for network decisions. Defaults to 0.
+        gwp_reduction_pct (float, optional): GWP reduction required. Defaults to 0.
+        gwp (float, optional): Base Case (Current) GWP. Defaults to 0.
 
-def global_warming_potential_network_reduction_constraint(instance: ConcreteModel, network_scale_level:int=0, gwp_reduction_pct:float= 0, gwp:float = 0):
+    Returns:
+        Constraint: global_warming_potential_network_reduction
+    """
     scales = scale_list(instance= instance, scale_levels= network_scale_level+1)
     
     def global_warming_potential_network_reduction_rule(instance, *scale_list):
         return instance.global_warming_potential_network[scale_list] <= gwp*(1 - gwp_reduction_pct/100)
-    instance.global_warming_potential_network_reduction_constraint = Constraint(*scales, rule = global_warming_potential_network_reduction_rule, doc= 'global warming potential for the whole network' )
+    instance.constraint_global_warming_potential_network_reduction = Constraint(*scales, rule = global_warming_potential_network_reduction_rule, doc= 'global warming potential for the whole network' )
     constraint_latex_render(global_warming_potential_network_reduction_rule)
-    return instance.global_warming_potential_network_reduction_constraint
+    return instance.constraint_global_warming_potential_network_reduction
 
 
 
-def carbon_emission_network_constraint(instance: ConcreteModel, network_scale_level:int=0) -> Constraint:
-    scales = scale_list(instance= instance, scale_levels = network_scale_level+1)
-    def carbon_emission_network_rule(instance, *scale_list):
-        return instance.carbon_emission_network[scale_list] == sum(instance.carbon_emission_location[location_, scale_list] for location_ in instance.locations)
-    instance.carbon_emission_network_constraint = Constraint(*scales, rule = carbon_emission_network_rule, doc = 'carbon_emission_network_process')
-    constraint_latex_render(carbon_emission_network_rule)
-    return instance.carbon_emission_network_constraint
-
diff --git a/src/energiapy/model/constraints/failure.py b/src/energiapy/model/constraints/failure.py
index 46c6aade..76f085bb 100644
--- a/src/energiapy/model/constraints/failure.py
+++ b/src/energiapy/model/constraints/failure.py
@@ -21,7 +21,7 @@
 from enum import Enum, auto
 
 
-def nameplate_production_failure_constraint(instance: ConcreteModel, fail_factor:dict = {}, network_scale_level: int = 0, scheduling_scale_level: int = 0) -> Constraint:
+def constraint_nameplate_production_failure(instance: ConcreteModel, fail_factor:dict = {}, network_scale_level: int = 0, scheduling_scale_level: int = 0) -> Constraint:
     """Determines production capacity utilization of facilities at location in network and capacity of facilities 
 
     Args:
@@ -31,7 +31,7 @@ def nameplate_production_failure_constraint(instance: ConcreteModel, fail_factor
         scheduling_scale_level (int, optional): scale of scheduling decisions. Defaults to 0.
 
     Returns:
-        Constraint: nameplate_production_failure_constraint
+        Constraint: nameplate_production_failure
     """
     scales = scale_list(instance=instance,
                         scale_levels=instance.scales.__len__())
@@ -44,7 +44,7 @@ def nameplate_production_failure_rule(instance, location, process, *scale_list):
                                scale_list[:network_scale_level+1]]
         else:
             return instance.P[location, process, scale_list[:scheduling_scale_level+1]] <= instance.Cap_P[location, process, scale_list[:network_scale_level+1]]
-    instance.nameplate_production_failure_constraint = Constraint(
+    instance.constraint_nameplate_production_failure = Constraint(
         instance.locations, instance.processes, *scales, rule=nameplate_production_failure_rule, doc='nameplate production capacity constraint')
     constraint_latex_render(nameplate_production_failure_rule)
-    return instance.nameplate_production_failure_constraint
+    return instance.constraint_nameplate_production_failure
diff --git a/src/energiapy/model/constraints/integer_cuts.py b/src/energiapy/model/constraints/integer_cuts.py
new file mode 100644
index 00000000..3099fddf
--- /dev/null
+++ b/src/energiapy/model/constraints/integer_cuts.py
@@ -0,0 +1,21 @@
+"""pyomo integer cuts
+"""
+
+__author__ = "Rahul Kakodkar"
+__copyright__ = "Copyright 2023, Multi-parametric Optimization & Control Lab"
+__credits__ = ["Rahul Kakodkar", "Efstratios N. Pistikopoulos"]
+__license__ = "Open"
+__version__ = "0.0.1"
+__maintainer__ = "Rahul Kakodkar"
+__email__ = "cacodcar@tamu.edu"
+__status__ = "Production"
+
+from pyomo.environ import ConcreteModel, Constraint
+from ...utils.latex_utils import constraint_latex_render
+from ...utils.scale_utils import scale_list
+from ...utils.scale_utils import scale_pyomo_set
+from ...utils.scale_utils import scale_tuple
+from ...components.location import Location
+from itertools import product
+from typing import Union
+from enum import Enum, auto
\ No newline at end of file
diff --git a/src/energiapy/model/constraints/inventory.py b/src/energiapy/model/constraints/inventory.py
index 62bb0d55..6f66a0a1 100644
--- a/src/energiapy/model/constraints/inventory.py
+++ b/src/energiapy/model/constraints/inventory.py
@@ -21,7 +21,7 @@
 from enum import Enum, auto
 
 
-def storage_facility_constraint(instance: ConcreteModel, store_max: dict, loc_res_dict: dict = {},  network_scale_level: int = 0) -> Constraint:
+def constraint_storage_facility(instance: ConcreteModel, store_max: dict, loc_res_dict: dict = {},  network_scale_level: int = 0) -> Constraint:
     """Determines where storage facility of certain capacity is inserted at location in network
 
     Args:
@@ -31,7 +31,7 @@ def storage_facility_constraint(instance: ConcreteModel, store_max: dict, loc_re
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: storage_facility_constraint
+        Constraint: storage_facility
     """
     scales = scale_list(instance=instance,
                         scale_levels=network_scale_level + 1)
@@ -43,12 +43,12 @@ def storage_facility_rule(instance, location, resource, *scale_list):
                              scale_list[:network_scale_level+1]]
         else:
             return instance.Cap_S[location, resource, scale_list[:network_scale_level+1]] == 0
-    instance.storage_facility_constraint = Constraint(
+    instance.constraint_storage_facility = Constraint(
         instance.locations, instance.resources_store, *scales, rule=storage_facility_rule, doc='storage facility sizing and location')
     constraint_latex_render(storage_facility_rule)
-    return instance.storage_facility_constraint
+    return instance.constraint_storage_facility
 
-def storage_facility_affix_constraint(instance: ConcreteModel, affix_storage_cap: dict, loc_res_dict: dict = {},  network_scale_level: int = 0) -> Constraint:
+def constraint_storage_facility_affix(instance: ConcreteModel, affix_storage_cap: dict, loc_res_dict: dict = {},  network_scale_level: int = 0) -> Constraint:
     """Determines where storage facility of certain capacity is inserted at location in network
 
     Args:
@@ -58,7 +58,7 @@ def storage_facility_affix_constraint(instance: ConcreteModel, affix_storage_cap
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: storage_facility_affix_constraint
+        Constraint: storage_facility_affix
     """
     scales = scale_list(instance=instance,
                         scale_levels=network_scale_level + 1)
@@ -72,13 +72,13 @@ def storage_facility_affix_rule(instance, location, resource, *scale_list):
                 return instance.Cap_S[location, resource, scale_list[:network_scale_level+1]] >= 0.0
         else:
             return instance.Cap_S[location, resource, scale_list[:network_scale_level+1]] == 0
-    instance.storage_facility_affix_constraint = Constraint(
+    instance.constraint_storage_facility_affix = Constraint(
         instance.locations, instance.resources_store, *scales, rule=storage_facility_affix_rule, doc='storage facility sizing and location')
     constraint_latex_render(storage_facility_affix_rule)
-    return instance.storage_facility_affix_constraint
+    return instance.constraint_storage_facility_affix
 
 
-def storage_facility_fix_constraint(instance: ConcreteModel, store_max: dict, storage_binaries: dict, loc_res_dict: dict = {},  network_scale_level: int = 0) -> Constraint:
+def constraint_storage_facility_fix(instance: ConcreteModel, store_max: dict, storage_binaries: dict, loc_res_dict: dict = {},  network_scale_level: int = 0) -> Constraint:
     """Determines where storage facility of certain capacity is inserted at location in network
 
     Args:
@@ -89,7 +89,7 @@ def storage_facility_fix_constraint(instance: ConcreteModel, store_max: dict, st
 
 
     Returns:
-        Constraint: storage_facility_fix_constraint
+        Constraint: storage_facility_fix
     """
     scales = scale_list(instance=instance,
                         scale_levels=network_scale_level + 1)
@@ -101,13 +101,13 @@ def storage_facility_fix_rule(instance, location, resource, *scale_list):
                                   scale_list[:network_scale_level+1])]
         else:
             return instance.Cap_S[location, resource, scale_list[:network_scale_level+1]] == 0
-    instance.storage_facility_fix_constraint = Constraint(
+    instance.constraint_storage_facility_fix = Constraint(
         instance.locations, instance.resources_store, *scales, rule=storage_facility_fix_rule, doc='storage facility sizing and location')
     constraint_latex_render(storage_facility_fix_rule)
-    return instance.storage_facility_fix_constraint
+    return instance.constraint_storage_facility_fix
 
 
-def min_storage_facility_constraint(instance: ConcreteModel, store_min: dict, loc_res_dict: dict = {},  network_scale_level: int = 0) -> Constraint:
+def constraint_min_storage_facility(instance: ConcreteModel, store_min: dict, loc_res_dict: dict = {},  network_scale_level: int = 0) -> Constraint:
     """Determines where storage facility of certain capacity is inserted at location in network
 
     Args:
@@ -118,7 +118,7 @@ def min_storage_facility_constraint(instance: ConcreteModel, store_min: dict, lo
 
 
     Returns:
-        Constraint: min_storage_facility_constraint
+        Constraint: min_storage_facility
     """
     scales = scale_list(instance=instance,
                         scale_levels=network_scale_level + 1)
@@ -130,13 +130,13 @@ def min_storage_facility_rule(instance, location, resource, *scale_list):
                              scale_list[:network_scale_level+1]]
         else:
             return Constraint.Skip
-    instance.min_storage_facility_constraint = Constraint(
+    instance.constraint_min_storage_facility = Constraint(
         instance.locations, instance.resources_store, *scales, rule=min_storage_facility_rule, doc='storage facility sizing and location')
     constraint_latex_render(min_storage_facility_rule)
-    return instance.min_storage_facility_constraint
+    return instance.constraint_min_storage_facility
 
 
-def nameplate_inventory_constraint(instance: ConcreteModel, loc_res_dict: dict = {}, network_scale_level: int = 0, scheduling_scale_level: int = 0) -> Constraint:
+def constraint_nameplate_inventory(instance: ConcreteModel, loc_res_dict: dict = {}, network_scale_level: int = 0, scheduling_scale_level: int = 0) -> Constraint:
     """Determines storage capacity utilization for resource at location in network and capacity of facilities 
 
     Args:
@@ -146,7 +146,7 @@ def nameplate_inventory_constraint(instance: ConcreteModel, loc_res_dict: dict =
         scheduling_scale_level (int, optional): scale of scheduling decisions. Defaults to 0.
 
     Returns:
-        Constraint: nameplate_inventory_constraint
+        Constraint: nameplate_inventory
     """
     scales = scale_list(instance=instance,
                         scale_levels=instance.scales.__len__())
@@ -156,7 +156,7 @@ def nameplate_inventory_rule(instance, location, resource, *scale_list):
             return instance.Inv[location, resource, scale_list[:scheduling_scale_level+1]] <= instance.Cap_S[location, resource, scale_list[:network_scale_level+1]]
         else:
             return instance.Inv[location, resource, scale_list[:scheduling_scale_level+1]] <= 0
-    instance.nameplate_inventory_constraint = Constraint(
+    instance.constraint_nameplate_inventory = Constraint(
         instance.locations, instance.resources_store, *scales, rule=nameplate_inventory_rule, doc='nameplate inventory capacity constraint')
     constraint_latex_render(nameplate_inventory_rule)
-    return instance.nameplate_inventory_constraint
+    return instance.constraint_nameplate_inventory
diff --git a/src/energiapy/model/constraints/land.py b/src/energiapy/model/constraints/land.py
index 69936262..c8e5f308 100644
--- a/src/energiapy/model/constraints/land.py
+++ b/src/energiapy/model/constraints/land.py
@@ -20,7 +20,7 @@
 from typing import Union
 from enum import Enum, auto
 
-def process_land_constraint(instance: ConcreteModel, land_dict: dict, network_scale_level: int = 0) -> Constraint:
+def constraint_process_land(instance: ConcreteModel, land_dict: dict, network_scale_level: int = 0) -> Constraint:
     """Land required for each process at location in network
 
     Args:
@@ -29,20 +29,20 @@ def process_land_constraint(instance: ConcreteModel, land_dict: dict, network_sc
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: process_land_constraint
+        Constraint: process_land
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def process_land_rule(instance, location, process, *scale_list):
         return instance.Land_process[location, process, scale_list] == land_dict[process]*instance.Cap_P[location, process, scale_list]
-    instance.process_land_constraint = Constraint(
+    instance.constraint_process_land = Constraint(
         instance.locations, instance.processes, *scales, rule=process_land_rule, doc='land required for process')
     constraint_latex_render(process_land_rule)
-    return instance.process_land_constraint
+    return instance.constraint_process_land
 
 
 
-def location_land_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+def constraint_location_land(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
     """Land required at each location in network
 
     Args:
@@ -50,20 +50,20 @@ def location_land_constraint(instance: ConcreteModel, network_scale_level: int =
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: location_land_constraint
+        Constraint: location_land
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def location_land_rule(instance, location, *scale_list):
         return instance.Land_location[location, scale_list] == sum(instance.Land_process[location, process_, scale_list] for process_ in instance.processes)
-    instance.location_land_constraint = Constraint(
+    instance.constraint_location_land = Constraint(
         instance.locations, *scales, rule=location_land_rule, doc='land required for process')
     constraint_latex_render(location_land_rule)
-    return instance.location_land_constraint
+    return instance.constraint_location_land
 
 
 
-def network_land_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+def constraint_network_land(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
     """Land required by network
 
     Args:
@@ -71,18 +71,18 @@ def network_land_constraint(instance: ConcreteModel, network_scale_level: int =
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: network_land_constraint
+        Constraint: network_land
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def network_land_rule(instance, *scale_list):
         return instance.Land_network[scale_list] == sum(instance.Land_location[location_, scale_list] for location_ in instance.locations)
-    instance.network_land_constraint = Constraint(
+    instance.constraint_network_land = Constraint(
         *scales, rule=network_land_rule, doc='land required for process')
     constraint_latex_render(network_land_rule)
-    return instance.network_land_constraint
+    return instance.constraint_network_land
 
-def location_land_restriction_constraint(instance: ConcreteModel, network_scale_level: int = 0, land_restriction: float = 0) -> Constraint:
+def constraint_location_land_restriction(instance: ConcreteModel, network_scale_level: int = 0, land_restriction: float = 0) -> Constraint:
     """Land required at each location in network
 
     Args:
@@ -90,13 +90,13 @@ def location_land_restriction_constraint(instance: ConcreteModel, network_scale_
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
  
     Returns:
-        Constraint: location_land_constraint
+        Constraint: location_land
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def location_land_restriction_rule(instance, location, *scale_list):
         return instance.Land_location[location, scale_list] <= land_restriction
-    instance.location_land_restriction_constraint = Constraint(
+    instance.constraint_location_land_restriction = Constraint(
         instance.locations, *scales, rule=location_land_restriction_rule, doc='land required for process')
     constraint_latex_render(location_land_restriction_rule)
-    return instance.location_land_restriction_constraint
\ No newline at end of file
+    return instance.constraint_location_land_restriction
\ No newline at end of file
diff --git a/src/energiapy/model/constraints/mode.py b/src/energiapy/model/constraints/mode.py
index ff6180d3..62132ae6 100644
--- a/src/energiapy/model/constraints/mode.py
+++ b/src/energiapy/model/constraints/mode.py
@@ -13,13 +13,74 @@
 from pyomo.environ import ConcreteModel, Constraint
 from ...utils.latex_utils import constraint_latex_render
 from ...utils.scale_utils import scale_list
-from ...utils.scale_utils import scale_pyomo_set
-from ...utils.scale_utils import scale_tuple
-from ...components.location import Location
-from itertools import product
-from typing import Union
-from enum import Enum, auto
 
+def constraint_production_mode(instance: ConcreteModel, mode_dict:dict, scheduling_scale_level: int = 0) -> Constraint:
+    """The sum of production through all modes equals production at scheduling scale
 
+    Args:
+        instance (ConcreteModel): pyomo model instance
+        mode_dict (dict): dictionary with modes available for process
+        scheduling_scale_level (int, optional): scale for scheduling decisions. Defaults to 0.
 
-# TODO Production mode constraints
+    Returns:
+        Constraint: production_mode
+    """
+
+    scales = scale_list(instance=instance, scale_levels=scheduling_scale_level+1)
+
+    def production_mode_rule(instance, location, process, *scale_list):
+        return instance.P[location, process, scale_list[:scheduling_scale_level+1]] == sum(instance.P_m[location, process, mode, scale_list[:scheduling_scale_level+1]] for mode in mode_dict[process])
+    instance.constraint_production_mode = Constraint(instance.locations, instance.processes, *scales, rule=production_mode_rule, doc='production mode sum constraint')
+    constraint_latex_render(production_mode_rule)
+    return instance.constraint_production_mode
+
+
+
+def constraint_production_mode_facility(instance: ConcreteModel, prod_max: dict, loc_pro_dict: dict = {}, scheduling_scale_level: int = 0) -> Constraint:
+    """Determines where production facility of certain capacity is mode for process at location in schedule
+
+    Args:
+        instance (ConcreteModel): pyomo instance
+        prod_max (dict): maximum production of process at location
+        loc_pro_dict (dict, optional): production facilities avaiable at location. Defaults to {}.
+        scheduling_scale_level (int, optional): scale of scheduling decisions. Defaults to 0.
+
+    Returns:
+        Constraint: production_facility_mode
+    """
+    scales = scale_list(instance=instance, scale_levels=scheduling_scale_level+1)
+
+    def production_mode_facility_rule(instance, location, process, mode, *scale_list):
+        if process in loc_pro_dict[location]:
+            if mode <= list(prod_max[location][process].keys())[-1:][0]:
+                return instance.Cap_P_m[location, process, mode, scale_list[:scheduling_scale_level+1]] <= prod_max[location][process][mode]*\
+                    instance.X_P_m[location, process, mode, scale_list[:scheduling_scale_level+1]]
+            else:
+                return Constraint.Skip
+        else:
+            return instance.Cap_P_m[location, process, mode, scale_list[:scheduling_scale_level+1]] == 0
+    instance.constraint_production_mode_facility = Constraint(
+        instance.locations, instance.processes, instance.modes, *scales, rule=production_mode_facility_rule, doc='production facility sizing and location')
+    constraint_latex_render(production_mode_facility_rule)
+    return instance.constraint_production_mode_facility
+
+def constraint_production_mode_binary(instance: ConcreteModel, mode_dict:dict, scheduling_scale_level: int = 0, network_scale_level: int = 0) -> Constraint:
+    """The sum of production through all modes equals production at scheduling scale
+
+    Args:
+        instance (ConcreteModel): pyomo model instance
+        mode_dict (dict): dictionary with modes available for process
+        scheduling_scale_level (int, optional): scale for scheduling decisions. Defaults to 0.
+        network_scale_level (int, optional): scale for network decisions. Defaults to 0.
+
+    Returns:
+        Constraint: production_mode_binary
+    """
+
+    scales = scale_list(instance=instance, scale_levels=scheduling_scale_level+1)
+
+    def production_mode_binary_rule(instance, location, process, *scale_list):
+        return instance.X_P[location, process, scale_list[:network_scale_level+1]] == sum(instance.X_P_m[location, process, mode, scale_list[:scheduling_scale_level+1]] for mode in mode_dict[process])
+    instance.constraint_production_mode_binary = Constraint(instance.locations, instance.processes, *scales, rule=production_mode_binary_rule, doc='production mode binary sum constraint')
+    constraint_latex_render(production_mode_binary_rule)
+    return instance.constraint_production_mode_binary
diff --git a/src/energiapy/model/constraints/production.py b/src/energiapy/model/constraints/production.py
index 3f487cfa..5a47db4f 100644
--- a/src/energiapy/model/constraints/production.py
+++ b/src/energiapy/model/constraints/production.py
@@ -21,7 +21,7 @@
 from enum import Enum, auto
 
 
-def production_facility_constraint(instance: ConcreteModel, prod_max: dict, loc_pro_dict: dict = {}, network_scale_level: int = 0) -> Constraint:
+def constraint_production_facility(instance: ConcreteModel, prod_max: dict, loc_pro_dict: dict = {}, network_scale_level: int = 0) -> Constraint:
     """Determines where production facility of certain capacity is inserted at location in network
 
     Args:
@@ -31,24 +31,24 @@ def production_facility_constraint(instance: ConcreteModel, prod_max: dict, loc_
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: production_facility_constraint
+        Constraint: production_facility
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def production_facility_rule(instance, location, process, *scale_list):
         if process in loc_pro_dict[location]:
-            return instance.Cap_P[location, process, scale_list[:network_scale_level+1]] <= prod_max[location][process] *\
+            return instance.Cap_P[location, process, scale_list[:network_scale_level+1]] <= prod_max[location][process][list(prod_max[location][process].keys())[-1:][0]] *\
                 instance.X_P[location, process,
                              scale_list[:network_scale_level+1]]
         else:
             return instance.Cap_P[location, process, scale_list[:network_scale_level+1]] == 0
-    instance.production_facility_constraint = Constraint(
+    instance.constraint_production_facility = Constraint(
         instance.locations, instance.processes, *scales, rule=production_facility_rule, doc='production facility sizing and location')
     constraint_latex_render(production_facility_rule)
-    return instance.production_facility_constraint
+    return instance.constraint_production_facility
 
 
-def production_facility_affix_constraint(instance: ConcreteModel, affix_production_cap: dict, loc_pro_dict: dict = {}, network_scale_level: int = 0) -> Constraint:
+def constraint_production_facility_affix(instance: ConcreteModel, affix_production_cap: dict, loc_pro_dict: dict = {}, network_scale_level: int = 0) -> Constraint:
     """affixes the capacity of production facilities
 
     Args:
@@ -58,7 +58,7 @@ def production_facility_affix_constraint(instance: ConcreteModel, affix_producti
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: production_facility_affix_constraint
+        Constraint: production_facility_affix
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1) 
     def production_facility_affix_rule(instance, location, process, *scale_list):
@@ -70,13 +70,13 @@ def production_facility_affix_rule(instance, location, process, *scale_list):
                 return instance.Cap_P[location, process, scale_list[:network_scale_level+1]] >= 0.0
         else:
             return instance.Cap_P[location, process, scale_list[:network_scale_level+1]] == 0
-    instance.production_facility_affix_constraint = Constraint(
+    instance.constraint_production_facility_affix = Constraint(
         instance.locations, instance.processes, *scales, rule=production_facility_affix_rule, doc='production facility sizing and location')
     constraint_latex_render(production_facility_affix_rule)
-    return instance.production_facility_affix_constraint
+    return instance.constraint_production_facility_affix
 
 
-def production_facility_fix_constraint(instance: ConcreteModel, prod_max: dict, production_binaries: dict, loc_pro_dict: dict = {}, network_scale_level: int = 0) -> Constraint:
+def constraint_production_facility_fix(instance: ConcreteModel, prod_max: dict, production_binaries: dict, loc_pro_dict: dict = {}, network_scale_level: int = 0) -> Constraint:
     """Determines where production facility of certain capacity is inserted at location in network
 
     Args:
@@ -86,7 +86,7 @@ def production_facility_fix_constraint(instance: ConcreteModel, prod_max: dict,
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: production_facility_fix_constraint
+        Constraint: production_facility_fix
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
@@ -97,13 +97,13 @@ def production_facility_fix_rule(instance, location, process, *scale_list):
                     location, process, *scale_list[:network_scale_level+1])]
         else:
             return instance.Cap_P[location, process, scale_list[:network_scale_level+1]] == 0
-    instance.production_facility_fix_constraint = Constraint(
+    instance.constraint_production_facility_fix = Constraint(
         instance.locations, instance.processes, *scales, rule=production_facility_fix_rule, doc='production facility sizing and location')
     constraint_latex_render(production_facility_fix_rule)
-    return instance.production_facility_fix_constraint
+    return instance.constraint_production_facility_fix
 
 
-def min_production_facility_constraint(instance: ConcreteModel, prod_min: dict, loc_pro_dict: dict = {}, network_scale_level: int = 0) -> Constraint:
+def constraint_min_production_facility(instance: ConcreteModel, prod_min: dict, loc_pro_dict: dict = {}, network_scale_level: int = 0) -> Constraint:
     """Determines where production facility of certain capacity is inserted at location in network
 
     Args:
@@ -113,7 +113,7 @@ def min_production_facility_constraint(instance: ConcreteModel, prod_min: dict,
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: min_production_facility_constraint
+        Constraint: min_production_facility
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
@@ -124,36 +124,41 @@ def min_production_facility_rule(instance, location, process, *scale_list):
                              scale_list[:network_scale_level+1]]
         else:
             return Constraint.Skip
-    instance.min_production_facility_constraint = Constraint(
+    instance.constraint_min_production_facility = Constraint(
         instance.locations, instance.processes, *scales, rule=min_production_facility_rule, doc='production facility sizing and location')
     constraint_latex_render(min_production_facility_rule)
-    return instance.min_production_facility_constraint
+    return instance.constraint_min_production_facility
 
-def nameplate_production_constraint(instance: ConcreteModel, capacity_factor: dict = {}, network_scale_level: int = 0, scheduling_scale_level: int = 0) -> Constraint:
+def constraint_nameplate_production(instance: ConcreteModel, capacity_factor: dict = {}, loc_pro_dict: dict = {}, network_scale_level: int = 0, scheduling_scale_level: int = 0) -> Constraint:
     """Determines production capacity utilization of facilities at location in network and capacity of facilities 
 
     Args:
         instance (ConcreteModel): pyomo instance
         capacity_factor (dict, optional): uncertain capacity availability training data. Defaults to {}.
+        loc_pro_dict (dict, optional): production facilities avaiable at location. Defaults to {}.
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
         scheduling_scale_level (int, optional): scale of scheduling decisions. Defaults to 0.
 
     Returns:
-        Constraint: nameplate_production_constraint
+        Constraint: nameplate_production
     """
     scales = scale_list(instance=instance,
                         scale_levels=instance.scales.__len__())
 
     def nameplate_production_rule(instance, location, process, *scale_list):
-        if process in instance.processes_varying:
-            return instance.P[location, process, scale_list[:scheduling_scale_level+1]] <= \
-                capacity_factor[location][process][scale_list[:scheduling_scale_level+1]] * \
-                instance.Cap_P[location, process,
-                               scale_list[:network_scale_level+1]]
+        if process in loc_pro_dict[location]:
+            if process in instance.processes_varying:
+                return instance.P[location, process, scale_list[:scheduling_scale_level+1]] <= \
+                    capacity_factor[location][process][scale_list[:scheduling_scale_level+1]] * \
+                    instance.Cap_P[location, process,
+                                scale_list[:network_scale_level+1]]
+            else:
+                return instance.P[location, process, scale_list[:scheduling_scale_level+1]] <= instance.Cap_P[location, process, scale_list[:network_scale_level+1]]
         else:
-            return instance.P[location, process, scale_list[:scheduling_scale_level+1]] <= instance.Cap_P[location, process, scale_list[:network_scale_level+1]]
-    instance.nameplate_production_constraint = Constraint(
+            return Constraint.Skip
+        
+    instance.constraint_nameplate_production = Constraint(
         instance.locations, instance.processes, *scales, rule=nameplate_production_rule, doc='nameplate production capacity constraint')
     constraint_latex_render(nameplate_production_rule)
-    return instance.nameplate_production_constraint
+    return instance.constraint_nameplate_production
 
diff --git a/src/energiapy/model/constraints/resource_balance.py b/src/energiapy/model/constraints/resource_balance.py
index 8e8a6738..1ebe6498 100644
--- a/src/energiapy/model/constraints/resource_balance.py
+++ b/src/energiapy/model/constraints/resource_balance.py
@@ -19,13 +19,11 @@
 from itertools import product
 from typing import Union
 from enum import Enum, auto
+from ...components.process import ProcessMode
 
-class ProcessMode(Enum):
-    single = auto() #only allows one mode
-    multi = auto() # allows multiple modes
 
 
-def resource_consumption_constraint(instance: ConcreteModel, loc_res_dict: dict = {}, cons_max: dict = {}, scheduling_scale_level: int = 0) -> Constraint:
+def constraint_resource_consumption(instance: ConcreteModel, loc_res_dict: dict = {}, cons_max: dict = {}, scheduling_scale_level: int = 0) -> Constraint:
     """Determines consumption of resource at location in network 
 
     Args:
@@ -35,7 +33,7 @@ def resource_consumption_constraint(instance: ConcreteModel, loc_res_dict: dict
         scheduling_scale_level (int, optional): scale of scheduling decisions. Defaults to 0.
 
     Returns:
-        Constraint: resource_consumption_constraint
+        Constraint: resource_consumption
     """
     scales = scale_list(instance=instance,
                         scale_levels=instance.scales.__len__())
@@ -45,13 +43,13 @@ def resource_consumption_rule(instance, location, resource, *scale_list):
             return instance.C[location, resource, scale_list[:scheduling_scale_level+1]] <= cons_max[location][resource]
         else:
             return instance.C[location, resource, scale_list[:scheduling_scale_level+1]] <= 0
-    instance.resource_consumption_constraint = Constraint(
+    instance.constraint_resource_consumption = Constraint(
         instance.locations, instance.resources_purch, *scales, rule=resource_consumption_rule, doc='resource consumption')
     constraint_latex_render(resource_consumption_rule)
-    return instance.resource_consumption_constraint
+    return instance.constraint_resource_consumption
 
 
-def resource_purchase_constraint(instance: ConcreteModel, cost_factor: dict = {}, price: dict = {},
+def constraint_resource_purchase(instance: ConcreteModel, cost_factor: dict = {}, price: dict = {},
                                  loc_res_dict: dict = {}, scheduling_scale_level: int = 0, expenditure_scale_level: int = 0) -> Constraint:
     """Determines expenditure on resource at location in network at the scheduling/expenditure scale
 
@@ -63,13 +61,13 @@ def resource_purchase_constraint(instance: ConcreteModel, cost_factor: dict = {}
         expenditure_scale_level (int, optional): scale of resource purchase decisions. Defaults to 0.
 
     Returns:
-        Constraint: resource_purchase_constraint
+        Constraint: resource_purchase
     """
     scales = scale_list(instance=instance,
                         scale_levels=instance.scales.__len__())
 
     def resource_purchase_rule(instance, location, resource, *scale_list):
-        if resource in instance.resources_varying.intersection(loc_res_dict[location]):
+        if resource in instance.resources_varying_price.intersection(loc_res_dict[location]):
             return instance.B[location, resource, scale_list[:scheduling_scale_level+1]] == price[location][resource] *\
                 cost_factor[location][resource][scale_list[:expenditure_scale_level+1]] * \
                 instance.C[location, resource,
@@ -79,14 +77,14 @@ def resource_purchase_rule(instance, location, resource, *scale_list):
                 return instance.B[location, resource, scale_list[:scheduling_scale_level+1]] == price[location][resource]*instance.C[location, resource, scale_list[:scheduling_scale_level+1]]
             else:
                 return instance.B[location, resource, scale_list[:scheduling_scale_level+1]] == 0
-    instance.resource_purchase_constraint = Constraint(
+    instance.constraint_resource_purchase = Constraint(
         instance.locations, instance.resources_purch, *scales, rule=resource_purchase_rule, doc='expenditure on purchase of resource')
     constraint_latex_render(resource_purchase_rule)
-    return instance.resource_purchase_constraint
+    return instance.constraint_resource_purchase
 
 
-def inventory_balance_constraint(instance: ConcreteModel, scheduling_scale_level: int = 0, \
-    conversion: dict = {}, cluster_wt: dict = None) -> Constraint:
+def constraint_inventory_balance(instance: ConcreteModel, scheduling_scale_level: int = 0, \
+    multiconversion: dict = {}, mode_dict: dict = {}, cluster_wt: dict = None) -> Constraint:
     """balances resource across the scheduling horizon
     Mass balance in any temporal discretization has the following within their respective sets:
     - consumption for resources that can be purchased
@@ -102,10 +100,12 @@ def inventory_balance_constraint(instance: ConcreteModel, scheduling_scale_level
     Args:
         instance (ConcreteModel): pyomo instance
         scheduling_scale_level (int, optional): scale of scheduling decisions. Defaults to 0.
-        conversion (dict, optional): unit conversion of resource by production facility. Defaults to {}.
-
+        multiconversion (dict, optional): unit conversion of resource by production facility. Defaults to {}.
+        mode_dict (dict, optional): dictionary with modes available. Defaults to {}.
+        cluster_wt (dict, optional): weight of cluster as determined through scenario aggregation. Defaults to None. 
+    
     Returns:
-        Constraint: inventory_balance_constraint
+        Constraint: inventory_balance
     """
     scales = scale_list(instance=instance,
                         scale_levels=instance.scales.__len__())
@@ -149,20 +149,20 @@ def inventory_balance_rule(instance, location, resource, *scale_list):
         # produced = sum(conversion[process][resource]*instance.P[location, process, scale_list[:scheduling_scale_level+1]] for process in instance.processes_singlem) \
         #     + sum(instance.P[location, process, scale_list[:scheduling_scale_level+1]] for process in instance.processes_multim)
         
-        produced = sum(conversion[process][resource]*instance.P[location, process,
-                       scale_list[:scheduling_scale_level+1]] for process in instance.processes_full) #includes processes + discharge
-
+        produced = sum(sum(multiconversion[process][mode][resource]*instance.P_m[location, process, mode,
+                       scale_list[:scheduling_scale_level+1]] for mode in mode_dict[process]) for process in instance.processes_full) #includes processes + discharge
+    
         if cluster_wt is not None:
             return cluster_wt[scale_list[:scheduling_scale_level+1]]*(consumption + produced - discharge + transport) == storage
         else:
             return consumption + produced - discharge + transport == storage
-    instance.inventory_balance_constraint = Constraint(
+    instance.constraint_inventory_balance = Constraint(
         instance.locations, instance.resources, *scales, rule=inventory_balance_rule, doc='mass balance across scheduling scale')
-    constraint_latex_render(inventory_balance_constraint)
-    return instance.inventory_balance_constraint
+    constraint_latex_render(inventory_balance_rule)
+    return instance.constraint_inventory_balance
 
 
-def demand_constraint(instance: ConcreteModel, demand: Union[dict, float], demand_factor: Union[dict, float], \
+def constraint_demand(instance: ConcreteModel, demand: Union[dict, float], demand_factor: Union[dict, float], \
     demand_scale_level: int = 0, scheduling_scale_level: int = 0, cluster_wt: dict = None) -> Constraint:
     """Ensures that demand for resource is met at chosen temporal scale
 
@@ -173,7 +173,7 @@ def demand_constraint(instance: ConcreteModel, demand: Union[dict, float], deman
         demand_dict (dict, optional): demand at location. Defaults to {}.
 
     Returns:
-        Constraint: demand_constraint
+        Constraint: demand
     """
     # scales = scale_list(instance= instance, scale_levels = demand_scale_level+1)
     scales = scale_list(instance=instance,
@@ -216,17 +216,17 @@ def demand_rule(instance, location, resource, *scale_list):
         return discharge >= demandtarget
 
     if len(instance.locations) > 1:
-        instance.demand_constraint = Constraint(
+        instance.constraint_demand = Constraint(
             instance.sinks, instance.resources_demand, *scales, rule=demand_rule, doc='specific demand for resources')
     else:
-        instance.demand_constraint = Constraint(
+        instance.constraint_demand = Constraint(
             instance.locations, instance.resources_demand, *scales, rule=demand_rule, doc='specific demand for resources')
     constraint_latex_render(demand_rule)
-    return instance.demand_constraint
+    return instance.constraint_demand
 
 
 
-def location_production_constraint(instance: ConcreteModel, cluster_wt: dict, network_scale_level: int = 0) -> Constraint:
+def constraint_location_production(instance: ConcreteModel, cluster_wt: dict, network_scale_level: int = 0) -> Constraint:
     """Determines total production capacity utilization at location
 
     Args:
@@ -234,7 +234,7 @@ def location_production_constraint(instance: ConcreteModel, cluster_wt: dict, ne
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: location_production_constraint
+        Constraint: location_production
     """
 
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
@@ -246,13 +246,13 @@ def location_production_rule(instance, location, process, *scale_list):
             return instance.P_location[location, process, scale_list] == sum(cluster_wt[scale_]*instance.P[location, process, scale_] for scale_ in scale_iter)
         else:
             return instance.P_location[location, process, scale_list] == sum(instance.P[location, process, scale_] for scale_ in scale_iter)
-    instance.location_production_constraint = Constraint(
+    instance.constraint_location_production = Constraint(
         instance.locations, instance.processes, *scales, rule=location_production_rule, doc='total production at location')
     constraint_latex_render(location_production_rule)
-    return instance.location_production_constraint
+    return instance.constraint_location_production
 
 
-def location_discharge_constraint(instance: ConcreteModel, cluster_wt: dict, network_scale_level: int = 0) -> Constraint:
+def constraint_location_discharge(instance: ConcreteModel, cluster_wt: dict, network_scale_level: int = 0) -> Constraint:
     """Determines total resource discharged/sold at locations in network
 
     Args:
@@ -260,7 +260,7 @@ def location_discharge_constraint(instance: ConcreteModel, cluster_wt: dict, net
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: location_discharge_constraint
+        Constraint: location_discharge
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
     scale_iter = scale_tuple(
@@ -271,13 +271,13 @@ def location_discharge_rule(instance, location, resource, *scale_list):
             return instance.S_location[location, resource, scale_list] == sum(cluster_wt[scale_]*instance.S[location, resource, scale_] for scale_ in scale_iter)
         else:
             return instance.S_location[location, resource, scale_list] == sum(instance.S[location, resource, scale_] for scale_ in scale_iter)
-    instance.location_discharge_constraint = Constraint(
+    instance.constraint_location_discharge = Constraint(
         instance.locations, instance.resources_sell, *scales, rule=location_discharge_rule, doc='total discharge at location')
     constraint_latex_render(location_discharge_rule)
-    return instance.location_discharge_constraint
+    return instance.constraint_location_discharge
 
 
-def location_consumption_constraint(instance: ConcreteModel, cluster_wt: dict, network_scale_level: int = 0) -> Constraint:
+def constraint_location_consumption(instance: ConcreteModel, cluster_wt: dict, network_scale_level: int = 0) -> Constraint:
     """Determines total resource consumed at locations in network
 
     Args:
@@ -285,7 +285,7 @@ def location_consumption_constraint(instance: ConcreteModel, cluster_wt: dict, n
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: location_consumption_constraint
+        Constraint: location_consumption
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
     scale_iter = scale_tuple(
@@ -296,13 +296,13 @@ def location_consumption_rule(instance, location, resource, *scale_list):
             return instance.C_location[location, resource, scale_list] == sum(cluster_wt[scale_]*instance.C[location, resource, scale_] for scale_ in scale_iter)
         else:
             return instance.C_location[location, resource, scale_list] == sum(instance.C[location, resource, scale_] for scale_ in scale_iter)
-    instance.location_consumption_constraint = Constraint(
+    instance.constraint_location_consumption = Constraint(
         instance.locations, instance.resources_purch, *scales, rule=location_consumption_rule, doc='total consumption at location')
     constraint_latex_render(location_consumption_rule)
-    return instance.location_consumption_constraint
+    return instance.constraint_location_consumption
 
 
-def location_purchase_constraint(instance: ConcreteModel, cluster_wt: dict, network_scale_level: int = 0) -> Constraint:
+def constraint_location_purchase(instance: ConcreteModel, cluster_wt: dict, network_scale_level: int = 0) -> Constraint:
     """Determines total resource purchase expenditure at locations in network
 
     Args:
@@ -310,7 +310,7 @@ def location_purchase_constraint(instance: ConcreteModel, cluster_wt: dict, netw
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: location_purchase_constraint
+        Constraint: location_purchase
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
     scale_iter = scale_tuple(
@@ -321,15 +321,15 @@ def location_purchase_rule(instance, location, resource, *scale_list):
             return instance.B_location[location, resource, scale_list] == sum(cluster_wt[scale_]*instance.B[location, resource, scale_] for scale_ in scale_iter)
         else:
             return instance.B_location[location, resource, scale_list] == sum(instance.B[location, resource, scale_] for scale_ in scale_iter)
-    instance.location_purchase_constraint = Constraint(
+    instance.constraint_location_purchase = Constraint(
         instance.locations, instance.resources_purch, *scales, rule=location_purchase_rule, doc='total purchase at location')
     constraint_latex_render(location_purchase_rule)
-    return instance.location_purchase_constraint
+    return instance.constraint_location_purchase
 
 
 # *-------------------------Network scale mass balance calculation constraints--------------------------
 
-def network_production_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+def constraint_network_production(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
     """Determines total production utilization across network
 
     Args:
@@ -337,19 +337,19 @@ def network_production_constraint(instance: ConcreteModel, network_scale_level:
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: network_production_constraint
+        Constraint: network_production
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def network_production_rule(instance, process, *scale_list):
         return instance.P_network[process, scale_list] == sum(instance.P_location[location_, process, scale_list] for location_ in instance.locations)
-    instance.network_production_constraint = Constraint(
+    instance.constraint_network_production = Constraint(
         instance.processes, *scales, rule=network_production_rule, doc='total production from network')
     constraint_latex_render(network_production_rule)
-    return instance.network_production_constraint
+    return instance.constraint_network_production
 
 
-def network_discharge_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+def constraint_network_discharge(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
     """Determines total resource discharged across network
 
     Args:
@@ -357,19 +357,19 @@ def network_discharge_constraint(instance: ConcreteModel, network_scale_level: i
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: network_discharge_constraint
+        Constraint: network_discharge
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def network_discharge_rule(instance, resource, *scale_list):
         return instance.S_network[resource, scale_list] == sum(instance.S_location[location_, resource, scale_list] for location_ in instance.locations)
-    instance.network_discharge_constraint = Constraint(
+    instance.constraint_network_discharge = Constraint(
         instance.resources_sell, *scales, rule=network_discharge_rule, doc='total discharge from network')
     constraint_latex_render(network_discharge_rule)
-    return instance.network_discharge_constraint
+    return instance.constraint_network_discharge
 
 
-def network_consumption_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+def constraint_network_consumption(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
     """Determines total resource consumed across network
 
     Args:
@@ -377,19 +377,19 @@ def network_consumption_constraint(instance: ConcreteModel, network_scale_level:
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: network_consumption_constraint
+        Constraint: network_consumption
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def network_consumption_rule(instance, resource, *scale_list):
         return instance.C_network[resource, scale_list] == sum(instance.C_location[location_, resource, scale_list] for location_ in instance.locations)
-    instance.network_consumption_constraint = Constraint(
+    instance.constraint_network_consumption = Constraint(
         instance.resources_purch, *scales, rule=network_consumption_rule, doc='total consumption from network')
     constraint_latex_render(network_consumption_rule)
-    return instance.network_consumption_constraint
+    return instance.constraint_network_consumption
 
 
-def network_purchase_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
+def constraint_network_purchase(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
     """Determines total purchase expenditure on resource across network
 
     Args:
@@ -397,13 +397,13 @@ def network_purchase_constraint(instance: ConcreteModel, network_scale_level: in
         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
 
     Returns:
-        Constraint: network_purchase_constraint
+        Constraint: network_purchase
     """
     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
 
     def network_purchase_rule(instance, resource, *scale_list):
         return instance.B_network[resource, scale_list] == sum(instance.B_location[location_, resource, scale_list] for location_ in instance.locations)
-    instance.network_purchase_constraint = Constraint(
+    instance.constraint_network_purchase = Constraint(
         instance.resources_purch, *scales, rule=network_purchase_rule, doc='total purchase from network')
     constraint_latex_render(network_purchase_rule)
-    return instance.network_purchase_constraint
+    return instance.constraint_network_purchase
diff --git a/src/energiapy/model/constraints/transport.py b/src/energiapy/model/constraints/transport.py
index 1e622d44..8af1dbbe 100644
--- a/src/energiapy/model/constraints/transport.py
+++ b/src/energiapy/model/constraints/transport.py
@@ -24,7 +24,17 @@
 
 
 # *-------------------------Transport constraints--------------------------
-def transport_export_constraint(instance: ConcreteModel, scheduling_scale_level: int = 0, transport_avail_dict: dict = {}) -> Constraint:
+def constraint_transport_export(instance: ConcreteModel, scheduling_scale_level: int = 0, transport_avail_dict: dict = {}) -> Constraint:
+    """Total resource exported equals amount transported through all modes
+
+    Args:
+        instance (ConcreteModel): pyomo model instance
+        scheduling_scale_level (int, optional): scale of scheduling decisions. Defaults to 0.
+        transport_avail_dict (dict, optional): Modes of transportation available between locations. Defaults to {}.
+
+    Returns:
+        Constraint: transport_export
+    """
     scales = scale_list(instance=instance,
                         scale_levels=scheduling_scale_level+1)
 
@@ -32,13 +42,23 @@ def transport_export_rule(instance, source, sink, resource, *scale_list):
         return instance.Exp[source, sink, resource, scale_list[:scheduling_scale_level+1]] == \
             sum(instance.Trans_exp[source, sink, resource, transport_, scale_list[:scheduling_scale_level+1]]
                 for transport_ in instance.transports.intersection(transport_avail_dict[(source, sink)]))
-    instance.transport_export_constraint = Constraint(instance.sources, instance.sinks,
+    instance.constraint_transport_export = Constraint(instance.sources, instance.sinks,
                                                       instance.resources_trans, *scales, rule=transport_export_rule, doc='export of resource from source to sink')
     constraint_latex_render(transport_export_rule)
-    return instance.transport_export_constraint
+    return instance.constraint_transport_export
+
 
+def constraint_transport_import(instance: ConcreteModel, scheduling_scale_level: int = 0, transport_avail_dict: dict = {}) -> Constraint:
+    """Total amount of resource imported
 
-def transport_import_constraint(instance: ConcreteModel, scheduling_scale_level: int = 0, transport_avail_dict: dict = {}) -> Constraint:
+    Args:
+        instance (ConcreteModel): pyomo model instance
+        scheduling_scale_level (int, optional): scale of scheduling decisions. Defaults to 0.
+        transport_avail_dict (dict, optional): Modes of transportation available between locations. Defaults to {}.
+
+    Returns:
+        Constraint: transport_import
+    """
     scales = scale_list(instance=instance,
                         scale_levels=scheduling_scale_level+1)
 
@@ -46,25 +66,45 @@ def transport_import_rule(instance, sink, source, resource, *scale_list):
         return instance.Imp[sink, source, resource, scale_list[:scheduling_scale_level+1]] == \
             sum(instance.Trans_imp[sink, source, resource, transport_, scale_list[:scheduling_scale_level+1]]
                 for transport_ in instance.transports.intersection(transport_avail_dict[(source, sink)]))
-    instance.transport_import_constraint = Constraint(instance.sinks, instance.sources,
+    instance.constraint_transport_import = Constraint(instance.sinks, instance.sources,
                                                       instance.resources_trans, *scales, rule=transport_import_rule, doc='import of resource from sink to source')
     constraint_latex_render(transport_import_rule)
-    return instance.transport_import_constraint
+    return instance.constraint_transport_import
+
 
+def constraint_transport_balance(instance: ConcreteModel, scheduling_scale_level: int = 0) -> Constraint:
+    """Imported resources from sink to source equals exported resources from sink to source
 
-def transport_balance_constraint(instance: ConcreteModel, scheduling_scale_level: int = 0) -> Constraint:
+    Args:
+        instance (ConcreteModel): pyomo model instance
+        scheduling_scale_level (int, optional): scale of scheduling decisions. Defaults to 0.
+
+    Returns:
+        Constraint: transport_balance
+    """
     scales = scale_list(instance=instance,
                         scale_levels=scheduling_scale_level+1)
 
     def transport_balance_rule(instance, sink, source, resource, *scale_list):
         return instance.Imp[sink, source, resource, scale_list[:scheduling_scale_level+1]] == instance.Exp[source, sink, resource, scale_list[:scheduling_scale_level+1]]
-    instance.transport_balance_constraint = Constraint(instance.sinks, instance.sources,
+    instance.constraint_transport_balance = Constraint(instance.sinks, instance.sources,
                                                        instance.resources_trans, *scales, rule=transport_balance_rule, doc='balances import and export from source to sinks')
     constraint_latex_render(transport_balance_rule)
-    return instance.transport_balance_constraint
+    return instance.constraint_transport_balance
+
+
+def constraint_transport_exp_UB(instance: ConcreteModel, scheduling_scale_level: int = 0, trans_max: dict = {}, transport_avail_dict: dict = {}) -> Constraint:
+    """Maximum resource that can be transported
 
+    Args:
+        instance (ConcreteModel): pyomo model instance
+        scheduling_scale_level (int, optional): scale of scheduling decisions. Defaults to 0.
+        trans_max (dict, optional): Maximum allowed transportation. Defaults to {}.
+        transport_avail_dict (dict, optional): Modes of transportation available between locations. Defaults to {}.
 
-def transport_exp_UB_constraint(instance: ConcreteModel, scheduling_scale_level: int = 0, trans_max: dict = {}, transport_avail_dict: dict = {}) -> Constraint:
+    Returns:
+        Constraint: transport_exp_UB
+    """
     scales = scale_list(instance=instance,
                         scale_levels=scheduling_scale_level+1)
 
@@ -73,13 +113,24 @@ def transport_exp_UB_rule(instance, source, sink, resource, transport, *scale_li
             return instance.Trans_exp[source, sink, resource, transport, scale_list[:scheduling_scale_level+1]] <= trans_max[transport]
         else:
             return instance.Trans_exp[source, sink, resource, transport, scale_list[:scheduling_scale_level+1]] <= 0
-    instance.transport_exp_UB_constraint = Constraint(instance.sources, instance.sinks, instance.resources_trans,
+    instance.constraint_transport_exp_UB = Constraint(instance.sources, instance.sinks, instance.resources_trans,
                                                       instance.transports, *scales, rule=transport_exp_UB_rule, doc='import of resource from sink to source')
     constraint_latex_render(transport_exp_UB_rule)
-    return instance.transport_exp_UB_constraint
+    return instance.constraint_transport_exp_UB
+
+
+def constraint_transport_imp_UB(instance: ConcreteModel, scheduling_scale_level: int = 0, trans_max: dict = {}, transport_avail_dict: dict = {}) -> Constraint:
+    """Maximum amount of resource that can be imported
 
+    Args:
+        instance (ConcreteModel): pyomo model instance
+        scheduling_scale_level (int, optional): scale of scheduling decisions. Defaults to 0.
+        trans_max (dict, optional): Maximum allowed transportation. Defaults to {}.
+        transport_avail_dict (dict, optional): Modes of transportation available between locations. Defaults to {}.
 
-def transport_imp_UB_constraint(instance: ConcreteModel, scheduling_scale_level: int = 0, trans_max: dict = {}, transport_avail_dict: dict = {}) -> Constraint:
+    Returns:
+        Constraint: transport_imp_UB
+    """
     scales = scale_list(instance=instance,
                         scale_levels=scheduling_scale_level+1)
 
@@ -88,7 +139,7 @@ def transport_imp_UB_rule(instance, sink, source, resource, transport, *scale_li
             return instance.Trans_imp[sink, source, resource, transport, scale_list[:scheduling_scale_level+1]] <= trans_max[transport]
         else:
             return instance.Trans_imp[sink, source, resource, transport, scale_list[:scheduling_scale_level+1]] <= 0
-    instance.transport_imp_UB_constraint = Constraint(instance.sinks, instance.sources, instance.resources_trans,
+    instance.constraint_transport_imp_UB = Constraint(instance.sinks, instance.sources, instance.resources_trans,
                                                       instance.transports, *scales, rule=transport_imp_UB_rule, doc='import of resource from sink to source')
     constraint_latex_render(transport_imp_UB_rule)
-    return instance.transport_imp_UB_constraint
+    return instance.constraint_transport_imp_UB
diff --git a/src/energiapy/model/constraints/uncertain.py b/src/energiapy/model/constraints/uncertain.py
index c6a8ca6c..bf64b3da 100644
--- a/src/energiapy/model/constraints/uncertain.py
+++ b/src/energiapy/model/constraints/uncertain.py
@@ -20,132 +20,7 @@
 from typing import Union
 from enum import Enum, auto
 
-# def demand_constraint_flex(instance: ConcreteModel, demand: int, demand_factor: Union[dict, float], \
-#     demand_scale_level: int = 0, scheduling_scale_level: int = 0) -> Constraint:
-#     """Ensures that demand for resource is met at chosen temporal scale
-
-#     Args:
-#         instance (ConcreteModel): pyomo instance
-#         demand_scale_level (int, optional): scale of demand decisions. Defaults to 0.
-#         scheduling_scale_level (int, optional): scale of scheduling decisions. Defaults to 0.
-#         demand_dict (dict, optional): demand at location. Defaults to {}.
-
-#     Returns:
-#         Constraint: demand_constraint
-#     """
-#     # scales = scale_list(instance= instance, scale_levels = demand_scale_level+1)
-#     scales = scale_list(instance=instance,
-#                         scale_levels=instance.scales.__len__())
-#     scale_iter = scale_tuple(
-#         instance=instance, scale_levels=scheduling_scale_level+1)
- 
-#     def demand_flex_rule(instance, location, resource, *scale_list):
-#         if type(demand_factor[location][list(demand_factor[location])[0]]) == float:
-#             return sum(instance.S[location, resource_, scale_list[:scheduling_scale_level+1]] for
-#                        resource_ in instance.resources_demand) == demand*demand_factor[location][scale_list[:demand_scale_level+1]] - instance.Demand_slack[location, scale_list[:demand_scale_level+1]]
-#         else:
-#             return sum(instance.S[location, resource, scale_] for scale_ in scale_iter if scale_[:scheduling_scale_level+1] == scale_list)\
-#                 == demand*demand_factor[location][resource][scale_list[:demand_scale_level+1]] - instance.Demand_slack[location, scale_list[:demand_scale_level+1]]
-
-#     if len(instance.locations) > 1:
-#         instance.demand_constraint_flex = Constraint(
-#             instance.sinks, instance.resources_demand, *scales, rule=demand_flex_rule, doc='specific demand for resources')
-#     else:
-#         instance.demand_constraint_flex = Constraint(
-#             instance.locations, instance.resources_demand, *scales, rule=demand_flex_rule, doc='specific demand for resources')
-#     constraint_latex_render(demand_flex_rule)
-#     return instance.demand_constraint_flex
-
-
-
-# def delta_cap_location_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
-#     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
-#     scale_iter = scale_tuple(
-#         instance=instance, scale_levels=instance.scales.__len__())
-
-#     def delta_cap_location_rule(instance, location, process, *scale_list):
-#         return instance.Delta_Cap_P_location[location, process, scale_list] == sum(instance.Delta_Cap_P[location, process, scale_] for scale_ in scale_iter)
-#     instance.delta_cap_location_constraint = Constraint(
-#         instance.locations, instance.processes_varying, *scales, rule=delta_cap_location_rule, doc='total transport cost across scale')
-#     constraint_latex_render(delta_cap_location_rule)
-#     return instance.delta_cap_location_constraint
-
-
-# def delta_cap_network_constraint(instance: ConcreteModel, network_scale_level: int = 0) -> Constraint:
-#     scales = scale_list(instance=instance, scale_levels=network_scale_level+1)
-
-#     def delta_cap_network_rule(instance, process, *scale_list):
-#         return instance.Delta_Cap_P_network[process, scale_list] == sum(instance.Delta_Cap_P_location[location_, process, scale_list] for location_ in instance.locations)
-#     instance.delta_cap_network_constraint = Constraint(
-#         instance.processes_varying, *scales, rule=delta_cap_network_rule, doc='total transport cost across scale')
-#     constraint_latex_render(delta_cap_network_rule)
-#     return instance.delta_cap_network_constraint
-
-
-
-# def uncertain_nameplate_production_constraint(instance: ConcreteModel, network_scale_level: int = 0, scheduling_scale_level: int = 0) -> Constraint:
-#     """Determines production capacity utilization of facilities at location in network and capacity of facilities 
-#     with uncertain capacility available for utilization
-
-#     Args:
-#         instance (ConcreteModel): pyomo instance
-#         network_scale_level (int, optional): scale of network decisions. Defaults to 0.
-#         scheduling_scale_level (int, optional): scale of scheduling decisions. Defaults to 0.
-
-#     Returns:
-#         Constraint: uncertain_nameplate_production_constraint
-#     """
-#     scales = scale_list(instance=instance,
-#                         scale_levels=instance.scales.__len__())
-
-#     def uncertain_nameplate_production_rule(instance, location, process, *scale_list):
-#         if process in instance.processes_varying:
-#             return instance.P[location, process, scale_list[:scheduling_scale_level+1]] <= instance.Cap_P[location, process, scale_list[:network_scale_level+1]]\
-#                 + instance.Delta_Cap_P[location, process,
-#                                        scale_list[:scheduling_scale_level+1]]
-#         else:
-#             return instance.P[location, process, scale_list[:scheduling_scale_level+1]] <= instance.Cap_P[location, process, scale_list[:network_scale_level+1]]
-#     instance.uncertain_nameplate_production_constraint = Constraint(
-#         instance.locations, instance.processes, *scales, rule=uncertain_nameplate_production_rule, doc='nameplate production capacity constraint')
-#     constraint_latex_render(uncertain_nameplate_production_rule)
-#     return instance.uncertain_nameplate_production_constraint
-
-
-# def uncertain_resource_purchase_constraint(instance: ConcreteModel, price: dict = {},  loc_res_dict: dict = {}, scheduling_scale_level: int = 0, expenditure_scale_level: int = 0) -> Constraint:
-#     """Determines expenditure on resource at location in network at the scheduling/expenditure scale
-#     with uncertainty in resource proce
-
-#     Args:
-#         instance (ConcreteModel): pyomo instance
-#         price (dict, optional): base price of resource. Defaults to {}.
-#         loc_res_dict (dict, optional): storage facilities for resource available at location. Defaults to {}.
-#         scheduling_scale_level (int, optional): scale of scheduling decisions. Defaults to 0.
-#         expenditure_scale_level (int, optional): scale of expenditure decisions. Defaults to 0.
-
-#     Returns:
-#         Constraint: uncertain_resource_purchase_constraint
-#     """
-#     scales = scale_list(instance=instance,
-#                         scale_levels=instance.scales.__len__())
-
-#     def uncertain_resource_purchase_rule(instance, location, resource, *scale_list):
-#         if resource in instance.resources_varying.intersection(loc_res_dict[location]):
-#             return instance.B[location, resource, scale_list[:scheduling_scale_level+1]] == price[location][resource] *\
-#                 instance.C[location, resource, scale_list[:scheduling_scale_level+1]] * \
-#                 instance.Delta_Cost_R[location, resource,
-#                                       scale_list[:scheduling_scale_level+1]]
-#         else:
-#             if resource in instance.resources_purch.intersection(loc_res_dict[location]):
-#                 return instance.B[location, resource, scale_list[:scheduling_scale_level+1]] == price[location][resource]*instance.C[location, resource, scale_list[:scheduling_scale_level+1]]
-#             else:
-#                 return instance.B[location, resource, scale_list[:scheduling_scale_level+1]] == 0
-#     instance.uncertain_resource_purchase_constraint = Constraint(
-#         instance.locations, instance.resources_purch, *scales, rule=uncertain_resource_purchase_rule, doc='expenditure on purchase of resource')
-#     constraint_latex_render(uncertain_resource_purchase_rule)
-#     return instance.uncertain_resource_purchase_constraint
-
-
-def uncertain_resource_demand_constraint(instance: ConcreteModel, demand:Union[dict, float], scheduling_scale_level: int = 0) -> Constraint:
+def constraint_uncertain_resource_demand(instance: ConcreteModel, demand:Union[dict, float], scheduling_scale_level: int = 0) -> Constraint:
     """Discharge meets an uncertain demand
 
     Args:
@@ -154,7 +29,7 @@ def uncertain_resource_demand_constraint(instance: ConcreteModel, demand:Union[d
         scheduling_scale_level (int, optional): scheduling scales of the problem. Defaults to 0.
 
     Returns:
-        Constraint: instance.uncertain_resource_demand_constraint
+        Constraint: instance.uncertain_resource_demand
     """
     
     scales = scale_tuple(instance=instance, scale_levels=scheduling_scale_level+1)
@@ -165,11 +40,11 @@ def uncertain_resource_demand_rule(instance, location, resource, *scale_list):
         else:
             return instance.S[location, resource, scale_list[:scheduling_scale_level+1]] >= demand*instance.resource_demand_uncertainty[location, resource, scale_list[:scheduling_scale_level+1]]
             
-    instance.uncertain_resource_demand_constraint = Constraint(instance.locations, instance.resources_uncertain_demand, *scales, rule= uncertain_resource_demand_rule,\
+    instance.constraint_uncertain_resource_demand = Constraint(instance.locations, instance.resources_uncertain_demand, *scales, rule= uncertain_resource_demand_rule,\
         doc = 'meet uncertain demand')
-    return instance.uncertain_resource_demand_constraint
+    return instance.constraint_uncertain_resource_demand
 
-def uncertain_process_capacity_constraint(instance: ConcreteModel, capacity: dict, network_scale_level: int = 0) -> Constraint:
+def constraint_uncertain_process_capacity(instance: ConcreteModel, capacity: dict, network_scale_level: int = 0) -> Constraint:
     """provides uncertainty to process capacity
 
     Args:
@@ -178,13 +53,13 @@ def uncertain_process_capacity_constraint(instance: ConcreteModel, capacity: dic
         network_scale_level (int, optional): network scales of the problem. Defaults to 0.
 
     Returns:
-        Constraint: _description_
+        Constraint: uncertain_process_capacity
     """
     scales = scale_tuple(instance=instance, scale_levels=network_scale_level+1)
     
     def uncertain_process_capacity_rule(instance, location, process, *scale_list):
         return instance.Cap_P[location, process, scale_list[:network_scale_level+1]] == capacity[location][process]*instance.process_capacity_uncertainty[location, process, scale_list[:network_scale_level+1]]
     
-    instance.uncertain_process_capacity_constraint = Constraint(instance.locations, instance.processes_uncertain_capacity, *scales, rule= uncertain_process_capacity_rule,\
+    instance.constraint_uncertain_process_capacity = Constraint(instance.locations, instance.processes_uncertain_capacity, *scales, rule= uncertain_process_capacity_rule,\
         doc = 'uncertain capacity for process')
-    return instance.uncertain_process_capacity_constraint
+    return instance.constraint_uncertain_process_capacity
diff --git a/src/energiapy/model/formulate.py b/src/energiapy/model/formulate.py
index 718f9128..05e297f9 100644
--- a/src/energiapy/model/formulate.py
+++ b/src/energiapy/model/formulate.py
@@ -28,20 +28,18 @@
 from .constraints.transport import *
 from .constraints.failure import *
 from .constraints.uncertain import *
+from .constraints.mode import *
 from .objectives import *
 from pyomo.environ import ConcreteModel
 from typing import  Set
 
 
-# class Variables(Enum):
-#     network = auto()
-#     schedule = auto()
-#     binary = auto()
-#     cost = auto()
-#     transport = auto()
-#     uncertain = auto()
-    
 class Constraints(Enum):
+    """Class of constraints
+
+    Args:
+        Enum (auto): choose set of contraints to add
+    """
     cost = auto()
     emission = auto()
     failure = auto()
@@ -55,11 +53,15 @@ class Constraints(Enum):
     lifecycle = auto()
 
 class Objective(Enum):
+    """Class of objective
+
+    Args:
+        Enum (auto): cost, demand, etc.
+    """
     cost = auto()
     demand = auto()
     
-def formulate(scenario: Scenario, constraints:Set[Constraints], objective:Objective, demand:float = 0.0001,\
-    land_restriction:float = 10**9, gwp:float = None, gwp_reduction_pct:float = None) -> ConcreteModel:
+def formulate(scenario: Scenario, constraints:Set[Constraints], objective:Objective, demand:float = 0.0001, gwp:float = None,  land_restriction: float = None, gwp_reduction_pct:float = None) -> ConcreteModel:
     """formulates a model
 
     Args:
@@ -82,15 +84,13 @@ def formulate(scenario: Scenario, constraints:Set[Constraints], objective:Object
     Objectives include:
             Objectives.cost
             Objectives.demand
-            
+      
     Returns:
         ConcreteModel: instance
     """
 
-    if type(scenario.demand) is dict:
-        demand = {i.name: {j.name: scenario.demand[i][j] for j in scenario.demand[i].keys()} for i in scenario.demand.keys()}
-    else:
-        demand = scenario.demand
+    if type(demand) is dict:
+        demand = {i.name: {j.name: demand[i][j] for j in demand[i].keys()} for i in demand.keys()}
         
     instance = ConcreteModel() 
     generate_sets(instance=instance, scenario= scenario)
@@ -104,157 +104,165 @@ def formulate(scenario: Scenario, constraints:Set[Constraints], objective:Object
     if Constraints.uncertain in constraints:
         generate_uncertainty_vars(instance = instance, scale_level = scenario.scheduling_scale_level)
 
-        uncertain_process_capacity_constraint(instance = instance, capacity = scenario.prod_max, network_scale_level = scenario.network_scale_level)
-        uncertain_resource_demand_constraint(instance = instance, demand = demand, scheduling_scale_level = scenario.scheduling_scale_level)
+        constraint_uncertain_process_capacity(instance = instance, capacity = scenario.prod_max, network_scale_level = scenario.network_scale_level)
+        constraint_uncertain_resource_demand(instance = instance, demand = demand, scheduling_scale_level = scenario.scheduling_scale_level)
     
     if Constraints.mode in constraints:
-        generate_mode_vars(instance=instance, scale_level=scenario.scheduling_scale_level, mode_dict= scenario.mode_dict)
-
+        generate_mode_vars(instance= instance, scale_level=scenario.scheduling_scale_level, mode_dict= scenario.mode_dict)
+        
+        constraint_production_mode(instance= instance, mode_dict= scenario.mode_dict, scheduling_scale_level= scenario.scheduling_scale_level)
+        constraint_production_mode_facility(instance= instance, prod_max= scenario.prod_max, loc_pro_dict= scenario.loc_pro_dict, scheduling_scale_level= scenario.scheduling_scale_level)
+        constraint_production_mode_binary(instance= instance, mode_dict= scenario.mode_dict, scheduling_scale_level= scenario.scheduling_scale_level, network_scale_level= scenario.network_scale_level)
     
     if len(scenario.location_set) > 1:
         generate_transport_vars(instance=instance, scale_level=scenario.scheduling_scale_level)
         
     if Constraints.cost in constraints:
-        process_capex_constraint(instance=instance, capex_dict=scenario.capex_dict,
+        constraint_process_capex(instance=instance, capex_dict=scenario.capex_dict,
                              network_scale_level=scenario.network_scale_level)
-        process_fopex_constraint(instance=instance, fopex_dict=scenario.fopex_dict,
+        constraint_process_fopex(instance=instance, fopex_dict=scenario.fopex_dict,
                                 network_scale_level=scenario.network_scale_level)
-        process_vopex_constraint(instance=instance, vopex_dict=scenario.vopex_dict,
+        constraint_process_vopex(instance=instance, vopex_dict=scenario.vopex_dict,
                                 network_scale_level=scenario.network_scale_level)
 
-        process_incidental_constraint(instance=instance, incidental_dict=scenario.incidental_dict,
+        constraint_process_incidental(instance=instance, incidental_dict=scenario.incidental_dict,
                                 network_scale_level=scenario.network_scale_level)
 
         
-        location_capex_constraint(
+        constraint_location_capex(
             instance=instance, network_scale_level=scenario.network_scale_level)
-        location_fopex_constraint(
+        constraint_location_fopex(
             instance=instance, network_scale_level=scenario.network_scale_level)
-        location_vopex_constraint(
+        constraint_location_vopex(
             instance=instance, network_scale_level=scenario.network_scale_level)
-        location_incidental_constraint(
+        constraint_location_incidental(
             instance=instance, network_scale_level=scenario.network_scale_level)
         
-        network_capex_constraint(
+        constraint_network_capex(
             instance=instance, network_scale_level=scenario.network_scale_level)
-        network_fopex_constraint(
+        constraint_network_fopex(
             instance=instance, network_scale_level=scenario.network_scale_level)
-        network_vopex_constraint(
+        constraint_network_vopex(
             instance=instance, network_scale_level=scenario.network_scale_level)
         
-        network_incidental_constraint(
+        constraint_network_incidental(
             instance=instance, network_scale_level=scenario.network_scale_level)
         
     if Constraints.emission in constraints:
-        global_warming_potential_process_constraint(
+        constraint_global_warming_potential_process(
             instance = instance, process_gwp_dict = scenario.process_gwp_dict, network_scale_level=scenario.network_scale_level)
         
-        global_warming_potential_resource_constraint(
+        constraint_global_warming_potential_resource(
             instance = instance, resource_gwp_dict = scenario.resource_gwp_dict, network_scale_level=scenario.network_scale_level)
 
-        global_warming_potential_material_constraint(
+        constraint_global_warming_potential_material(
             instance = instance, material_gwp_dict = scenario.material_gwp_dict, process_material_dict = scenario.process_material_dict, network_scale_level=scenario.network_scale_level)
 
-        global_warming_potential_location_constraint(
+        constraint_global_warming_potential_location(
             instance=instance, network_scale_level=scenario.network_scale_level)
 
-        global_warming_potential_network_constraint(
+        constraint_global_warming_potential_network(
             instance=instance, network_scale_level=scenario.network_scale_level)
 
     if Constraints.failure in constraints:
-        nameplate_production_failure_constraint(instance=instance, fail_factor=scenario.fail_factor,
+        constraint_nameplate_production_failure(instance=instance, fail_factor=scenario.fail_factor,
                                 network_scale_level=scenario.network_scale_level, scheduling_scale_level=scenario.scheduling_scale_level)
 
     if Constraints.inventory in constraints:
-        nameplate_inventory_constraint(instance=instance, loc_res_dict=scenario.loc_res_dict, network_scale_level=scenario.network_scale_level,
+        constraint_nameplate_inventory(instance=instance, loc_res_dict=scenario.loc_res_dict, network_scale_level=scenario.network_scale_level,
                                 scheduling_scale_level=scenario.scheduling_scale_level)
 
-        storage_facility_constraint(instance=instance, store_max=scenario.store_max,
+        constraint_storage_facility(instance=instance, store_max=scenario.store_max,
                             loc_res_dict=scenario.loc_res_dict, network_scale_level=scenario.network_scale_level)
 
-        min_storage_facility_constraint(instance=instance, store_min=scenario.store_min,
+        constraint_min_storage_facility(instance=instance, store_min=scenario.store_min,
                                         loc_res_dict=scenario.loc_res_dict, network_scale_level=scenario.network_scale_level)
 
     if Constraints.production in constraints:
-        nameplate_production_constraint(instance=instance, capacity_factor=scenario.capacity_factor,
+        constraint_nameplate_production(instance=instance, capacity_factor=scenario.capacity_factor, loc_pro_dict=scenario.loc_pro_dict,
                                 network_scale_level=scenario.network_scale_level, scheduling_scale_level=scenario.scheduling_scale_level)
-        production_facility_constraint(instance=instance, prod_max=scenario.prod_max,
+        constraint_production_facility(instance=instance, prod_max=scenario.prod_max,
                                 loc_pro_dict=scenario.loc_pro_dict, network_scale_level=scenario.network_scale_level)
-        min_production_facility_constraint(instance=instance, prod_min=scenario.prod_min,
+        constraint_min_production_facility(instance=instance, prod_min=scenario.prod_min,
                                     loc_pro_dict=scenario.loc_pro_dict, network_scale_level=scenario.network_scale_level)
 
     if Constraints.land in constraints:
-        process_land_constraint(instance=instance, land_dict=scenario.land_dict,
+        constraint_process_land(instance=instance, land_dict=scenario.land_dict,
                         network_scale_level=scenario.network_scale_level)
-        location_land_constraint(
+        constraint_location_land(
             instance=instance, network_scale_level=scenario.network_scale_level)
-        network_land_constraint(
+        constraint_network_land(
             instance=instance, network_scale_level=scenario.network_scale_level)
         
-        location_land_restriction_constraint(
-            instance=instance, network_scale_level=scenario.network_scale_level, land_restriction= land_restriction)
+        constraint_process_land_cost(instance=instance, land_dict=scenario.land_dict, land_cost_dict = scenario.land_cost_dict,\
+            network_scale_level=scenario.network_scale_level)
+        constraint_location_land_cost(
+            instance=instance, network_scale_level=scenario.network_scale_level)
+        constraint_network_land_cost(
+            instance=instance, network_scale_level=scenario.network_scale_level)
+        
+        if land_restriction is not None:
+            constraint_location_land_restriction(instance=instance, network_scale_level=scenario.network_scale_level, land_restriction= land_restriction)
 
     if Constraints.resource_balance in constraints:
         
-        inventory_balance_constraint(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
-                                    conversion=scenario.conversion)
+        constraint_inventory_balance(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
+                                    multiconversion=scenario.multiconversion, mode_dict= scenario.mode_dict)
 
-        resource_consumption_constraint(instance=instance, loc_res_dict=scenario.loc_res_dict,
+        constraint_resource_consumption(instance=instance, loc_res_dict=scenario.loc_res_dict,
                                 cons_max=scenario.cons_max, scheduling_scale_level=scenario.scheduling_scale_level)
         
-        resource_purchase_constraint(instance=instance, cost_factor=scenario.cost_factor, price=scenario.price,
+        constraint_resource_purchase(instance=instance, cost_factor=scenario.cost_factor, price=scenario.price,
                                     loc_res_dict=scenario.loc_res_dict, scheduling_scale_level=scenario.scheduling_scale_level,
                                     expenditure_scale_level=scenario.expenditure_scale_level)
 
-        location_production_constraint(
+        constraint_location_production(
             instance=instance, network_scale_level=scenario.network_scale_level, cluster_wt=scenario.cluster_wt)
-        location_discharge_constraint(
+        constraint_location_discharge(
             instance=instance, network_scale_level=scenario.network_scale_level, cluster_wt=scenario.cluster_wt)
-        location_consumption_constraint(
+        constraint_location_consumption(
             instance=instance, network_scale_level=scenario.network_scale_level, cluster_wt=scenario.cluster_wt)
-        location_purchase_constraint(
+        constraint_location_purchase(
             instance=instance, network_scale_level=scenario.network_scale_level, cluster_wt=scenario.cluster_wt)
 
-        network_production_constraint(
+        constraint_network_production(
             instance=instance, network_scale_level=scenario.network_scale_level)
-        network_discharge_constraint(
+        constraint_network_discharge(
             instance=instance, network_scale_level=scenario.network_scale_level)
-        network_consumption_constraint(
+        constraint_network_consumption(
             instance=instance, network_scale_level=scenario.network_scale_level)
-        network_purchase_constraint(
+        constraint_network_purchase(
             instance=instance, network_scale_level=scenario.network_scale_level)
 
     if Constraints.transport in constraints:
         
         if len(scenario.location_set) > 1:
-            transport_export_constraint(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
+            constraint_transport_export(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
                                         transport_avail_dict=scenario.transport_avail_dict)
-            transport_import_constraint(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
+            constraint_transport_import(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
                                         transport_avail_dict=scenario.transport_avail_dict)
-            transport_exp_UB_constraint(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
+            constraint_transport_exp_UB(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
                                         trans_max=scenario.trans_max, transport_avail_dict=scenario.transport_avail_dict)
-            transport_imp_UB_constraint(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
+            constraint_transport_imp_UB(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
                                         trans_max=scenario.trans_max, transport_avail_dict=scenario.transport_avail_dict)
-            transport_balance_constraint(
+            constraint_transport_balance(
                 instance=instance, scheduling_scale_level=scenario.scheduling_scale_level)
 
-            transport_exp_cost_constraint(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
-                                        trans_cost=scenario.trans_cost, distance_dict=scenario.distance_dict)
-            transport_imp_cost_constraint(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
+            constraint_transport_imp_cost(instance=instance, scheduling_scale_level=scenario.scheduling_scale_level,
                                         trans_cost=scenario.trans_cost, distance_dict=scenario.distance_dict)
-            transport_cost_constraint(
+            constraint_transport_cost(
                 instance=instance, scheduling_scale_level=scenario.scheduling_scale_level)
-            transport_cost_network_constraint(
+            constraint_transport_cost_network(
                 instance=instance, network_scale_level=scenario.network_scale_level)
     
     if gwp is not None:
-        global_warming_potential_network_reduction_constraint(instance= instance, network_scale_level = scenario.network_scale_level\
+        constraint_global_warming_potential_network_reduction(instance= instance, network_scale_level = scenario.network_scale_level\
             , gwp_reduction_pct = gwp_reduction_pct, gwp = gwp)
 
             
     if objective == Objective.cost:
         
-        demand_constraint(instance=instance, demand_scale_level=scenario.demand_scale_level,
+        constraint_demand(instance=instance, demand_scale_level=scenario.demand_scale_level,
                     scheduling_scale_level=scenario.scheduling_scale_level, demand = demand, demand_factor=scenario.demand_factor)
         
         cost_objective(instance=instance,
diff --git a/src/energiapy/model/objectives.py b/src/energiapy/model/objectives.py
index 427df044..5a141e2c 100644
--- a/src/energiapy/model/objectives.py
+++ b/src/energiapy/model/objectives.py
@@ -35,12 +35,15 @@ def cost_objective_rule(instance):
         incidental = sum(instance.Incidental_network[scale_] for scale_ in scale_iter)
         
         cost_purch = sum(instance.B_network[resource_, scale_] for resource_, scale_ in product(instance.resources_purch, scale_iter))
+        
+        land_cost = sum(instance.Land_cost_network[scale_] for scale_ in scale_iter) 
+        
         if len(instance.locations) > 1:
             cost_trans = sum(instance.Trans_cost_network[transport_, scale_] for transport_, scale_ in product(instance.transports, scale_iter))
         else:
             cost_trans = 0
-        return capex + vopex + fopex + cost_purch + cost_trans + incidental
-    instance.cost_objective = Objective(rule = cost_objective_rule, doc = 'total purchase from network')
+        return capex + vopex + fopex + cost_purch + cost_trans + incidental + land_cost
+    instance.cost_objective = Objective(rule = cost_objective_rule, doc = 'total cost')
     constraint_latex_render(cost_objective_rule)
     return instance.cost_objective
 
diff --git a/src/energiapy/model/sets.py b/src/energiapy/model/sets.py
index fbd5733c..e07eb67b 100644
--- a/src/energiapy/model/sets.py
+++ b/src/energiapy/model/sets.py
@@ -15,7 +15,7 @@
 from ..components.temporal_scale import Temporal_scale
 from pyomo.environ import ConcreteModel, Set
 from enum import Enum, auto
-
+from itertools import product
 
 def generate_sets(instance: ConcreteModel, scenario:Scenario):
     """Generates pyomo sets based on declared lists.
@@ -76,9 +76,9 @@ def generate_sets(instance: ConcreteModel, scenario:Scenario):
     """
 
     instance.scales = Set(scenario.scales.list, initialize = scenario.scales.scale, doc = 'set of scales')
-
-    sets = scenario.set_dict
     
+    sets = scenario.set_dict
+       
     instance.processes = Set(initialize  = sets['processes'], doc = 'Set of processes')
     instance.processes_full = Set(initialize = sets['processes_full'], doc = 'Set of all processes including dummy discharge')
     instance.resources = Set(initialize  = sets['resources'], doc = 'Set of resources')
@@ -86,7 +86,8 @@ def generate_sets(instance: ConcreteModel, scenario:Scenario):
     instance.resources_sell = Set(initialize = sets['resources_sell'], doc = 'Set of dischargeable resources')
     instance.resources_store = Set(initialize = sets['resources_store'], doc = 'Set of storeable resources')
     instance.resources_purch = Set(initialize = sets['resources_purch'], doc = 'Set of purchased resources')   
-    instance.resources_varying = Set(initialize = sets['resources_varying'], doc = 'Set of resources with varying purchase price')  
+    instance.resources_varying_price = Set(initialize = sets['resources_varying_price'], doc = 'Set of resources with varying purchase price') 
+    instance.resources_varying_demand = Set(initialize = sets['resources_varying_demand'], doc = 'Set of resources with varying purchase price')   
     instance.resources_demand = Set(initialize = sets['resources_demand'], doc = 'Set of resources with exact demand')    
     instance.processes_varying = Set(initialize  = sets['processes_varying'], doc = 'Set of processes with varying capacity')
     instance.processes_failure = Set(initialize  = sets['processes_failure'], doc = 'Set of processes which can fail')
@@ -100,6 +101,12 @@ def generate_sets(instance: ConcreteModel, scenario:Scenario):
     instance.resources_uncertain_demand = Set(initialize = sets['resources_uncertain_demand'], doc = 'Set of resources with uncertain demand')
     instance.processes_uncertain_capacity= Set(initialize = sets['processes_uncertain_capacity'], doc = 'Set of processes with uncertain capacity')
     
+    mode_lens  = []
+    for i,j in product(scenario.process_set, scenario.location_set):
+        mode_lens.append(len(scenario.prod_max[j.name][i.name].keys()))
+        
+    instance.modes = Set(initialize = list(range(max(mode_lens))), doc = 'Set of process modes')
+    
     
     if scenario.source_locations is not None:
         instance.sources = Set(initialize = sets['sources'], doc = 'Set of sources')
diff --git a/src/energiapy/model/variables/binary.py b/src/energiapy/model/variables/binary.py
index 99cf64c4..24228a4e 100644
--- a/src/energiapy/model/variables/binary.py
+++ b/src/energiapy/model/variables/binary.py
@@ -18,12 +18,6 @@ def generate_network_binary_vars(instance: ConcreteModel, scale_level:int= 0):
     """declares the following binary variables:
 
     scales_network_binary: Set of scales to define the network level
-
-    .. math::
-        X^{P}_{location, process, scale_{network}} \\textnormal{Process binary, 1 if process is set up, 0 otherwise}
-    
-    .. math::
-        X^{S}_{location, resource, scale_{network}}: \\textnormal{Storage binary variable, 1 if storage for resource is set up, 0 otherwise}
     
     Args:
         instance (ConcreteModel): pyomo instance
diff --git a/src/energiapy/model/variables/mode.py b/src/energiapy/model/variables/mode.py
index e05a4850..3eb9f4a2 100644
--- a/src/energiapy/model/variables/mode.py
+++ b/src/energiapy/model/variables/mode.py
@@ -31,4 +31,5 @@ def generate_mode_vars(instance: ConcreteModel, scale_level:int = 0, mode_dict:d
     """
     instance.P_m = Var(instance.locations, [(i,j) for i in mode_dict.keys() for j in mode_dict[i]], instance.scales_scheduling, within = NonNegativeReals, doc = 'Production modes')
     instance.X_P_m = Var(instance.locations, [(i,j) for i in mode_dict.keys() for j in mode_dict[i]], instance.scales_scheduling, within = Binary, doc = 'Production mode binaries')
+    instance.Cap_P_m = Var(instance.locations, [(i,j) for i in mode_dict.keys() for j in mode_dict[i]], instance.scales_scheduling, within = Binary, doc = 'Production mode binaries')
     return 
\ No newline at end of file
diff --git a/src/energiapy/model/variables/network.py b/src/energiapy/model/variables/network.py
index 5c72ef0e..5f25739f 100644
--- a/src/energiapy/model/variables/network.py
+++ b/src/energiapy/model/variables/network.py
@@ -25,10 +25,15 @@ def generate_network_vars(instance: ConcreteModel, scale_level:int = 0):
     instance.Cap_P = Var(instance.locations, instance.processes, instance.scales_network, within=NonNegativeReals, doc='Process Capacity')
     instance.Cap_S = Var(instance.locations, instance.resources_store, instance.scales_network, within=NonNegativeReals, doc='Storage Capacity')
     
-    instance.Land_process = Var(instance.locations, instance.processes, instance.scales_network, within=NonNegativeReals, doc='Land used by Process')
+    instance.Land_process = Var(instance.locations, instance.processes, instance.scales_network, within=NonNegativeReals, doc='Land cost by Process')
     instance.Land_location = Var(instance.locations, instance.scales_network, within=NonNegativeReals, doc='Land used at location')
     instance.Land_network = Var(instance.scales_network, within=NonNegativeReals, doc='Land used at network')
     
+    instance.Land_cost_process = Var(instance.locations, instance.processes, instance.scales_network, within=NonNegativeReals, doc='Land used by Process')
+    instance.Land_cost_location = Var(instance.locations, instance.scales_network, within=NonNegativeReals, doc='Land cost at location')
+    instance.Land_cost_network = Var(instance.scales_network, within=NonNegativeReals, doc='Land cost at network')
+    
+    
     instance.P_location = Var(instance.locations, instance.processes, instance.scales_network, within=NonNegativeReals, doc='Total production at location')
     instance.S_location = Var(instance.locations, instance.resources_sell, instance.scales_network, within=NonNegativeReals, doc='Total resource discharge at location')
     instance.C_location = Var(instance.locations, instance.resources_purch, instance.scales_network, within=NonNegativeReals, doc='Total resource consumption at location')
diff --git a/src/energiapy/model/variables/uncertain.py b/src/energiapy/model/variables/uncertain.py
index 7e1bfe89..ab19c2c8 100644
--- a/src/energiapy/model/variables/uncertain.py
+++ b/src/energiapy/model/variables/uncertain.py
@@ -15,9 +15,6 @@
 from itertools import product
 from ...utils.scale_utils import scale_pyomo_set
 
-from ...components.process import UncertainProcess
-from ...components.resource import UncertainResource
-
 
 def generate_uncertainty_vars(instance:ConcreteModel, scale_level:int= 0):
     """declares pyomo variables for uncertainty analysis of processes and resources
diff --git a/src/energiapy/plot/__init__.py b/src/energiapy/plot/__init__.py
index 2ab457df..e69de29b 100644
--- a/src/energiapy/plot/__init__.py
+++ b/src/energiapy/plot/__init__.py
@@ -1,11 +0,0 @@
-"""energiapy.plot init file
-"""
-
-__author__ = "Rahul Kakodkar"
-__copyright__ = "Copyright 2022, Multi-parametric Optimization & Control Lab"
-__credits__ = ["Rahul Kakodkar", "Efstratios N. Pistikopoulos"]
-__license__ = "Open"
-__version__ = "0.0.1"
-__maintainer__ = "Rahul Kakodkar"
-__email__ = "cacodcar@tamu.edu"
-__status__ = "Production"
diff --git a/src/energiapy/plot/plot.py b/src/energiapy/plot/plot.py
index 21460a68..fa5650d5 100644
--- a/src/energiapy/plot/plot.py
+++ b/src/energiapy/plot/plot.py
@@ -1,17 +1,17 @@
-#%%
 """plotting module
 """
 
 __author__ = "Rahul Kakodkar"
-__copyright__ = "Copyright 2022, Multi-parametric Optimization & Control Lab"
+__copyright__ = "Copyright 2023, Multi-parametric Optimization & Control Lab"
 __credits__ = ["Rahul Kakodkar", "Efstratios N. Pistikopoulos"]
 __license__ = "Open"
-__version__ = "0.0.1"
+__version__ = "1.0.4"
 __maintainer__ = "Rahul Kakodkar"
 __email__ = "cacodcar@tamu.edu"
 __status__ = "Production"
 
 from matplotlib import rc
+from numpy import zeros, array
 import matplotlib.pyplot as plt
 from ..utils.plot_utils import axis_formatter
 from ..components.process import Process
@@ -19,9 +19,30 @@
 from ..components.result import Result
 from ..components.location import Location
 from typing import Union
+from enum import Enum, auto
 
 
+class CostX(Enum):
+    """X axis for cost plot
 
+    Args:
+        Enum (_type_): location-wise or process-wise
+    """
+    location_wise = auto()
+    process_wise = auto()
+    
+class CostY(Enum):
+    """Y axis for cost plot
+    
+    Args:
+        Enum (_type_): capex, fopex, vopex, or total
+    """
+    total = auto()
+    capex = auto()
+    vopex = auto()
+    fopex = auto()
+    
+    
 
 def capacity_factor(process: Process, location: Location, \
     fig_size:tuple = (12,6), font_size:int = 16, color:str ='blue', usetex:bool = False):
@@ -109,12 +130,22 @@ def schedule(results: Result, y_axis:str, component:str, location:str,\
 
     Args:
         result (dict): dictionary that can be taken from result object 
+        y_axis (str): the y-axis, can be production (P), inventory (I), sales (S), consumption (C)
         component (str): resource or process name
         location (str): location name
         font_size (int, optional): font size. Defaults to 16.
         fig_size (tuple, optional): figure size. Defaults to (12,6).
         color (str, optional): color of plot. Defaults to 'blue'.
         usetex (bool, optional): False, if using latex font, need Tex set up (prone to errors). Defaults to 'False'.
+    
+    Examples:
+        For production the component has to be a process. For the rest use the appropriate resource
+        
+        Note that results plotting requires the string names to be provided as opposed to energiapy objects
+               
+        >>> plot.schedule(results= results, component='Power', y_axis= 'S', location= 'Goa')
+        
+        >>> plot.schedule(results= results, component='PV', y_axis= 'P', location= 'Goa')
         
     """
     rc('font', **{'family': 'serif', 'serif': ['Computer Modern'], 'size': font_size})
@@ -144,15 +175,15 @@ def contribution(results: Result, y_axis:str, location:str,\
     """generates a plot for scheduling result
 
     Args:
-    #     result (dict): dictionary that can be taken from result object 
-    #     component (str): resource or process name
-    #     location (str): location name
-    #     font_size (int, optional): font size. Defaults to 16.
-    #     fig_size (tuple, optional): figure size. Defaults to (12,6).
-    #     color (str, optional): color of plot. Defaults to 'blue'.
-    #     usetex (bool, optional): False, if using latex font, need Tex set up (prone to errors). Defaults to 'False'.
+        result (dict): dictionary that can be taken from result object 
+        component (str): resource or process name
+        location (str): location name
+        font_size (int, optional): font size. Defaults to 16.
+        fig_size (tuple, optional): figure size. Defaults to (12,6).
+        color (str, optional): color of plot. Defaults to 'blue'.
+        usetex (bool, optional): False, if using latex font, need Tex set up (prone to errors). Defaults to 'False'.
         
-    # """
+    """
     rc('font', **{'family': 'serif', 'serif': ['Computer Modern'], 'size': font_size})
     rc('text', usetex=usetex)
     fig, ax = plt.subplots(figsize= fig_size)
@@ -176,15 +207,15 @@ def capacity_utilization(results: Result, location:str, process:str = None,\
     """generates a plot for scheduling result
 
     Args:
-    #     result (dict): dictionary that can be taken from result object 
-    #     component (str): resource or process name
-    #     location (str): location name
-    #     font_size (int, optional): font size. Defaults to 16.
-    #     fig_size (tuple, optional): figure size. Defaults to (12,6).
-    #     color (str, optional): color of plot. Defaults to 'blue'.
-    #     usetex (bool, optional): False, if using latex font, need Tex set up (prone to errors). Defaults to 'False'.
+        result (dict): dictionary that can be taken from result object 
+        component (str): resource or process name
+        location (str): location name
+        font_size (int, optional): font size. Defaults to 16.
+        fig_size (tuple, optional): figure size. Defaults to (12,6).
+        color (str, optional): color of plot. Defaults to 'blue'.
+        usetex (bool, optional): False, if using latex font, need Tex set up (prone to errors). Defaults to 'False'.
         
-    # """
+    """
     if process is not None:
         rc('font', **{'family': 'serif', 'serif': ['Computer Modern'], 'size': font_size})
         rc('text', usetex=usetex)
@@ -222,18 +253,125 @@ def capacity_utilization(results: Result, location:str, process:str = None,\
         plt.rcdefaults()
     return
 
+def transport(results: Result, source:str, sink:str, resource: str, transport: str, fig_size:tuple = (12,6), font_size:int = 16, color:str ='blue', usetex:bool = False):
+    """Plots the transportation schedule from source to sink of choice for resource through a transportation mode
+
+    Args:
+        results (Result): results
+        source (str): source location
+        sink (str): sink location
+        resource (str): Resource that is being transported 
+        transport (str): Transport being used
+        fig_size (tuple, optional): Defaults to (12,6).
+        font_size (int, optional): Defaults to 16.
+        color (str, optional): Defaults to 'blue'.
+        usetex (bool, optional): Defaults to False.
+    
+    Examples:
 
+        Plotting transport is fairly straight forward. This plots the export from source to sink. 
+    
+        >>> plot.transport(results= results, source= 'Goa', sink= 'Texas', resource= 'PhDStudents', transport= 'GradSchool')
+    
+    """
+    
+    rc('font', **{'family': 'serif', 'serif': ['Computer Modern'], 'size': font_size})
+    rc('text', usetex=usetex)
+    fig, ax = plt.subplots(figsize= fig_size)
+    y_ = [results.output['Trans_exp'][i] for i in results.output['Trans_exp'].keys() if list(i)[:4] == [source, sink, resource, transport]]
+    plt.plot(y_)
+    plt.ylabel(f"Amount in unit basis")
+    plt.xlabel(f"Scheduling scale")
+    plt.title(f"Schedule for {resource} transported on {transport} from {source} to {sink}")
+    plt.xticks(rotation = 90)
+    plt.grid(alpha=0.3)
+    plt.rcdefaults()
+    return 
 
-#TODO - plots are independent of scales, check
 #TODO - make bar plots / pie plots for contribution from different components 
 #TODO - make layered scheduling plot for comparison 
 #TODO - make scenario comparison plots, perhaps use kwargs, allow n number of comparisons 
 
 
+def cost(results:Result,x: CostX, y: CostY, location: str = None, fig_size:tuple = (12,6), bar_width: float = 0.5, font_size:int = 16, color:str ='blue', usetex:bool = False):
+    """Plots the cost of processes, such as capex, vopex, fopex, or total
 
+    Args:
+        results (Result): results 
+        x (CostX): one of CostX.location_wise, CostX.process_wise 
+        y (CostY): one of CostY.total, CostY.capex, CostY.fopex, CostY.vopex
+        location (str, optional): location to plot for, applicable for CostX.process_wise. Defaults to None.
+        fig_size (tuple, optional): Defaults to (12,6).
+        bar_width (float, optional): Defaults to 0.5.
+        font_size (int, optional): Defaults to 16.
+        color (str, optional): Defaults to 'blue'.
+        usetex (bool, optional): Defaults to False.
+    """
+    
+    if y is CostY.capex:
+        res_dict = results.output['Capex_process']
+    if y is CostY.vopex:
+        res_dict = results.output['Vopex_process']
+    if y is CostY.fopex:
+        res_dict = results.output['Fopex_process']
+
+    if y is CostY.total:
+        res_dict = {i: results.output['Capex_process'][i] + results.output['Vopex_process'][i] + \
+            results.output['Fopex_process'][i] for i in results.output['Capex_process'].keys()}
+    
+    if x == CostX.process_wise:
+
+        x_,y_,n_ = ([] for _ in range(3))
+        for i in res_dict.keys():
+            if i[0] == location:
+                if res_dict[i]> 0:
+                    y_.append(res_dict[i])
+                    x_.append(i[1])
+                else:
+                    n_.append(i[1])
+                    
+        rc('font', **{'family': 'serif', 'serif': ['Computer Modern'], 'size': font_size})
+        rc('text', usetex=usetex)
+        fig, ax = plt.subplots(figsize= fig_size)
+        ax.bar(x_,y_, width = bar_width, zorder = 3)
+        y_plot = ''.join(str(y).split('CostY.')[1])
+        plt.title(f'Process-wise {y_plot} cost at {location}')
+        plt.ylabel(f'Unit Currency')
+        plt.xlabel(f'Processes')
+        plt.yscale('log')
+        plt.grid(alpha = 0.3, zorder = 0)
+        plt.rcdefaults()
+        
+    if x == CostX.location_wise:
+        
+        locations = tuple(results.components['locations'].keys())
 
+        weight_counts = dict()
 
+        for i in list(results.components['processes'].keys()):
+            vals = []
+            for j in res_dict.keys():
+                if j[1] == i:
+                    vals.append(res_dict[j]) 
+            weight_counts[i] = array(vals)        
+            
+        rc('font', **{'family': 'serif', 'serif': ['Computer Modern'], 'size': font_size})
+        rc('text', usetex=usetex)
+        fig, ax = plt.subplots(figsize= fig_size)
+        bottom = zeros(len(locations))
+
+        for boolean, weight_count in weight_counts.items():
+            p = ax.bar(locations, weight_count,  width = bar_width, label=boolean, bottom=bottom, zorder = 3)
+            bottom += weight_count
+        y_plot = ''.join(str(y).split('CostY.')[1])
+        plt.title(f'Location-wise {y_plot}')
+        plt.ylabel(f'Unit Currency')
+        plt.xlabel(f'Locations')
+        plt.yscale('log')
+        plt.legend()
+        plt.grid(alpha = 0.3, zorder = 0)
+        plt.rcdefaults()
+        plt.plot()
+    return
 
-# %%
 
-# %%
\ No newline at end of file
diff --git a/src/energiapy/utils/__init__.py b/src/energiapy/utils/__init__.py
index 6f8dd24e..e69de29b 100644
--- a/src/energiapy/utils/__init__.py
+++ b/src/energiapy/utils/__init__.py
@@ -1,11 +0,0 @@
-"""energiapy.utils init file
-"""
-
-__author__ = "Rahul Kakodkar"
-__copyright__ = "Copyright 2022, Multi-parametric Optimization & Control Lab"
-__credits__ = ["Rahul Kakodkar", "Efstratios N. Pistikopoulos"]
-__license__ = "Open"
-__version__ = "0.0.1"
-__maintainer__ = "Rahul Kakodkar"
-__email__ = "cacodcar@tamu.edu"
-__status__ = "Production"
diff --git a/src/energiapy/utils/cluster_utils.py b/src/energiapy/utils/cluster_utils.py
index 30fde958..954b733f 100644
--- a/src/energiapy/utils/cluster_utils.py
+++ b/src/energiapy/utils/cluster_utils.py
@@ -1,4 +1,3 @@
-# %%
 """Clustering utilities
 """
 
@@ -12,213 +11,8 @@
 __status__ = "Production"
 
 import pandas
-from numpy import polyfit, array_split, zeros, inf, min, argmin, ndarray
+import numpy
 from itertools import product
-from typing import Union
-from sklearn.cluster import AgglomerativeClustering
-from sklearn.neighbors import NearestCentroid
-from ..utils.math_utils import scaler, generate_connectivity_matrix, find_euclidean_distance
-from ..components.scenario import Scenario
-from ..components.resource import Resource
-from ..components.process import Process
-from ..components.temporal_scale import Temporal_scale
-from ..components.network import Network
-from ..components.location import Location
-from enum import Enum, auto
-import matplotlib.pyplot as plt
-
-class Clustermethod(Enum):
-    agg_hierarchial = auto()
-    dynamic_warping = auto()
-
-    
-
-def agg_hierarchial(scales: Temporal_scale, scale_level: int, periods: int, include:list, cost_factor: dict = None, \
-    capacity_factor: dict = None, demand_factor: dict = None):
-    """perform agglomerative hierarchial clustering over time-series data
-
-    Args:
-        scales (Temporal_scale): scales of the problme
-        scale_level (int): scale level to cluster at
-        periods (int): number of clustering periods
-        cost_factor (dict, optional): factor to account for varying cost factors. Defaults to None.
-        capacity_factor (dict, optional): factor to account for varying production capacity. Defaults to None.
-
-    Returns:
-        _type_: _description_
-    """
-    if 'cost' in include:
-        cost_factor_df = pandas.DataFrame(cost_factor)
-    else:
-        cost_factor_df = None
-    
-    if 'capacity' in include:    
-        capacity_factor_df = pandas.DataFrame(capacity_factor)
-    else:
-        capacity_factor_df = None
-    
-    if 'demand' in include:
-        demand_factor_df = pandas.DataFrame(demand_factor)
-    else:
-        demand_factor_df = None
-    
- 
-    
-    df = pandas.concat([cost_factor_df, capacity_factor_df, demand_factor_df],
-                       axis=1).reset_index(drop=True) # makes a common data frame with all different data sets
-
-    parent_scale = scales.scale[scale_level-1]
-    scale = scales.scale[scale_level] # the scale for which to cluster, e.g.: day
-    
-    child_scale = scales.scale[scale_level+1] # the lower scale which is nested under the parent scale, e.g. : hour
-        
-    split_df = array_split(df, len(parent_scale))
-    # output_df = pandas.DataFrame()
-    # rep_dict_iter = {i: 0 for i in range(len(parent_scale))} 
-    
-    rep_dict_iter = {}    
-       
-    
-    #creates a new Temporal_scale object on the reduced scale
-    reduced_dicretization_list = list(scales.discretization_list)
-    reduced_dicretization_list[scale_level] = periods
-    reduced_temporal_scale = Temporal_scale(reduced_dicretization_list)
-
-    reduced_scenario_scaleiter = array_split(reduced_temporal_scale.scale_iter(scale_level= scale_level +1), len(parent_scale))
-    for parent_iter in range(len(parent_scale)):
-        
-        if len(df) == len(parent_scale)*len(scale):
-            scaled_df = scaler(input_df=split_df[parent_iter], scale = scale)
-        
-        else:
-            scaled_df = scaler(input_df=split_df[parent_iter], scale = scale,
-                        child_scale=child_scale) # reshapes the data frame, e.g. instead of 8760 linear data points, creates a 365x24 matrix
-        
-        scaled_array = scaled_df.to_numpy() # makes an array instead of a dataframe, as raw data is better handled in other libraries, removes dependencies 
-
-        connectivity_matrix = generate_connectivity_matrix(scale_len= len(scale)) # make a matrix to ensure that chronology is maintained 
-        ahc = AgglomerativeClustering(n_clusters=periods, affinity='euclidean', connectivity=connectivity_matrix,
-                                    linkage='ward', compute_full_tree=True)
-        clustered_array = ahc.fit_predict(scaled_array)
-        cluster_labels = ahc.labels_  # get list of representative days
-
-        nearest_centroid_array = NearestCentroid().fit(
-            scaled_array, clustered_array).centroids_  # get the centroids of the clusters
-        nearest_centroid_df = pandas.DataFrame(
-            data=nearest_centroid_array, columns=[col for col in scaled_df])
-        # add cluster numbers to the dataset
-        scaled_df['cluster_no'] = cluster_labels
-        nearest_centroid_df['cluster_no'] = scaled_df['cluster_no'].unique()
-
-        euclidean_distance_list = []
-        closest_to_centroid_df = pandas.DataFrame(
-            columns=['period', 'rep_period', 'cluster_wt'])
-
-        for cluster_no in nearest_centroid_df['cluster_no']:
-            # -1 skips cluster_no row
-            cluster_array = scaled_df[scaled_df['cluster_no']
-                                    == cluster_no].iloc[:, :-1].to_numpy()
-            centroid_array = nearest_centroid_df[nearest_centroid_df['cluster_no']
-                                                == cluster_no].iloc[:, :-1].to_numpy()
-            centroid = centroid_array.tolist()[0]
-            for cluster in cluster_array: # finding euclidean distance from each data point to centroid 
-                cluster_point = [cluster for cluster in cluster]
-                euclidean_distance = find_euclidean_distance(
-                    cluster_point, centroid)
-                euclidean_distance_list.append(euclidean_distance)
-
-        scaled_df['ED'] = euclidean_distance_list #find the actual day closest to centroid
-        closest_to_centroid_df['period'] = (
-            scaled_df['cluster_no'].value_counts().index.values)
-        cluster_wts = [i for i in scaled_df['cluster_no'].value_counts()]
-        closest_to_centroid_df['cluster_wt'] = cluster_wts
-
-        list_ = []
-        for i in closest_to_centroid_df['period']:
-            list_.append(
-                scaled_df.index.values[scaled_df['ED'] == scaled_df['ED'][scaled_df['cluster_no'] == i].min()][0])#find the actual day closest to centroid
-        closest_to_centroid_df['rep_period'] = list_
-        closest_to_centroid_df = closest_to_centroid_df.sort_values(
-            by=['rep_period']).reset_index(drop=True)
-        closest_to_centroid_df['period'] = list(
-            range(len(closest_to_centroid_df['period'])))
-
-        
-        current_iter = [ tuple(i) for i in reduced_scenario_scaleiter[parent_iter]]
-   
-        output_dict= {scale: {'rep_period': (parent_iter, \
-            closest_to_centroid_df['rep_period'][closest_to_centroid_df['period'] == scale[scale_level]].values[0], *scale[scale_level+1:]),
-                            'cluster_wt': closest_to_centroid_df['cluster_wt'][closest_to_centroid_df['period'] == scale[scale_level]].values[0]}\
-                                for scale in current_iter}
-
-        rep_dict_iter = {**rep_dict_iter, **output_dict} #V. important: clusters each index in the parent scale individually and combines dict
-    # rep_dict = {rep_dict_iter[i].values() for i}
-
-    # puts all relevant data in a dictionary
-
-    
-    wcss_sum = sum(i for i in scaled_df['ED'])/periods # collects the error data
-
-    info_dict = {
-        'wcss_sum': wcss_sum
-    }
-    
-    return rep_dict_iter, reduced_temporal_scale, info_dict
-
-# TODO - call methods as a function
-# TODO  - Handle multiple outputs
-
-
-def agg_hierarchial_elbow(scenario: Scenario, location: Location, scale_level: int, include: list, range_list: list = None) -> list:
-    """calculate the error of a particular clustering method over a range of different cluster periods
-
-    Args:
-        scales (Temporal_scale): scales of the problem
-        scale_level (int): scale over which to cluster
-        cost_factor (dict, optional): factor for varying resource cost. Defaults to None.
-        capacity_factor (dict, optional): factor for varying production capacities. Defaults to None.
-        range_list (list, optional): range of clustering days over which to compute error. Defaults to None.
-
-    Returns:
-        list: error for each cluster period returned as a list
-    """
-    if range_list is None:
-        iter_ = scenario.scales.scale[scale_level]
-    else:
-        iter_ = range_list
-    wcss_list  = []
-    for i in iter_:
-        rep_dict_iter, reduced_temporal_scale, info_dict = agg_hierarchial(scales = scenario.scales, scale_level=scale_level, periods=i,
-                                 cost_factor= scenario.cost_factor[location.name], capacity_factor= scenario.capacity_factor[location.name], \
-                                     demand_factor= scenario.demand_factor[location.name], include= include)
-        wcss_list.append(info_dict['wcss_sum'])
-        
-        
-    # wcss_list = [agg_hierarchial(scales = scenario.scales, scale_level=scale_level, periods=i,
-    #                              cost_factor= scenario.cost_factor[location.name], capacity_factor= scenario.capacity_factor[location.name], \
-    #                                  demand_factor= scenario.demand_factor[location.name], include= include) for i in iter_]
-    
-    
-    theta  = polyfit(x= range_list,  y= wcss_list, deg=3)
-    # theta  = np.polyfit(x=[i+10 for i in range(70)],  y= wcss1, deg=3)
-
-    # y_line = [theta[2] + theta[1] * pow(x, 1) + theta[0] * pow(x, 2) for x in [i+10 for i in range(70)]]
-    y_line = [theta[3] + theta[2] * pow(x, 1) + theta[1] * pow(x, 2) + theta[0] * pow(x, 3) for x in range_list]
-
-    # y_line = theta[3] + theta[2] * pow(xdata, 1) + theta[1] * pow(xdata, 2) +  theta[0] * pow(xdata, 3)
-
-    fig, ax = plt.subplots(figsize=(8, 6))
-    x = range_list
-    ax.plot(x, y_line)
-    ax.scatter(x, wcss_list, color = 'indianred')
-    plt.title('Clustering using AHC for Houston')
-    plt.xlabel('Cluster Size')
-    plt.ylabel('WCSS')
-    plt.grid(alpha=0.3)
-    fig.show()
-    
-    
-    return wcss_list
 
 
 def dynamic_warping_matrix(series1: list, series2: list):
@@ -232,23 +26,23 @@ def dynamic_warping_matrix(series1: list, series2: list):
     Returns:
         matrix: cost matrix for dtw
     """
-    matrix = zeros((len(series1) + 1, len(series2) + 1))
+    matrix = numpy.zeros((len(series1) + 1, len(series2) + 1))
     for i, j in product(range(1, len(series1)+1), range(1, len(series2) + 1)):
-        matrix[i, j] = inf
+        matrix[i, j] = numpy.inf
     matrix[0, 0] = 0
     for i, j in product(range(1, len(series1)+1), range(1, len(series2) + 1)):
         cost = abs(series1[i-1] - series2[j-1])
-        prev = min([matrix[i-1, j], matrix[i, j-1], matrix[i-1, j-1]])
+        prev = numpy.min([matrix[i-1, j], matrix[i, j-1], matrix[i-1, j-1]])
         matrix[i, j] = cost + prev
     return matrix
 
 
 
-def dynamic_warping_path(matrix: ndarray) -> list:
+def dynamic_warping_path(matrix: numpy.ndarray) -> list:
     """finds optimal warping path from a dynamic time warping cost matrix 
 
     Args:
-        matrix (ndarray): cost matrix from application of dtw
+        matrix (numpy.ndarray): cost matrix from application of dtw
 
     Returns:
         list: optimal path with list of coordinates
@@ -257,7 +51,7 @@ def dynamic_warping_path(matrix: ndarray) -> list:
     i, j = len(matrix) - 1, len(matrix[0]) - 1
     path.append([i, j])
     while i > 0 and j > 0:
-        index_min = argmin(
+        index_min = numpy.argmin(
             [matrix[i-1, j], matrix[i, j-1], matrix[i-1, j-1]])
         if index_min == 0:
             i = i - 1
@@ -269,168 +63,3 @@ def dynamic_warping_path(matrix: ndarray) -> list:
         path.append([i, j])
     # path.append([0,0])
     return path
-
-def dynamic_warping(source_scenario: Scenario, target_scenario: Scenario, \
-    scale_level: int, include: list, aspect: Union[Resource, Process], reference_dict: dict = None):
-    
-    source_location = list(source_scenario.location_set)[0] 
-    target_location = list(target_scenario.location_set)[0]
-
-    if 'cost' in include:
-        source_series = list(source_scenario.cost_factor[source_location.name][aspect.name].values())
-        target_series = list(target_scenario.cost_factor[target_location.name][aspect.name].values())
-        
-        reference_keys = target_scenario.cost_factor[target_location.name][aspect.name].keys()
-    
-    elif 'capacity' in include:    
-        source_series = list(source_scenario.capacity_factor[source_location.name][aspect.name].values())
-        target_series = list(target_scenario.capacity_factor[target_location.name][aspect.name].values())
-        
-        reference_keys = target_scenario.capacity_factor[target_location.name][aspect.name].keys()
-        
-
-    elif 'demand' in include:
-        source_series = list(source_scenario.demand_factor[source_location.name][aspect.name].values())
-        target_series = list(target_scenario.demand_factor[target_location.name][aspect.name].values())
-        
-        reference_keys = target_scenario.demand_factor[target_location.name][aspect.name].keys()
-        
-    matrix = dynamic_warping_matrix(series1= source_series, series2= target_series)
-    
-    path = dynamic_warping_path(matrix= matrix)
-    
-    path = [i for i in path if i[0] != 0] #remove zeroth value
-    
-
-    
-    #collects the x and y coordinates of path as separate lists
-    x_ = [path[i][0] -1 for i in range(len(path))] 
-    y_ = [path[i][1] -1 for i in range(len(path))] 
-    
-    
-    #reverse the coordinates 
-    x_.reverse() 
-    y_.reverse()
-    
-    # xr = x_[:-1]
-    # yr = y_[:-1]
-    
-    
-    i_list = list(range(len(target_series)))
-    
-    
-    j_list = list(reference_keys)
-    source_values = [source_series[i] for i in x_]
- 
-    
-    target_values = [target_series[i] for i in y_]
-
-    # print(len(target_values))
-
-
-    # reduced_temporal_scale = Temporal_scale(discretization_list=[1, len(target_values), 24])
-    
-    # iter_temporal_scale = target_scenario.scales
-    
-    reduced_temporal_scale = target_scenario.scales
-    
-    reduced_scenario_scaleiter = [(i) for i in product(*[reduced_temporal_scale.scale[i] for i in reduced_temporal_scale.scale])]
-    
-    
-    #TODO Check this 
-    counts = {j:  y_.count(i) for i,j in zip(i_list, j_list)}
-    
-    # print(counts)
-    
-    # #TODO find actual rep_day
-    # if reference_dict is not None:
-    rep_dict = {j: {'rep_period': reference_dict[j]['rep_period'], 'cluster_wt': counts[j[:scale_level]]} for j in reduced_scenario_scaleiter}
-    #     # rep_dict = {j: {'rep_period': j, 'cluster_wt': 1} for j in reduced_scenario_scaleiter}
-    # else:
-    # rep_dict = {j: {'rep_period': j, 'cluster_wt': counts[j[:scale_level]]} for j in reduced_scenario_scaleiter}
-            
-    
-    info_dict = {
-        'matrix': matrix,
-        'path': path,
-        'input_data': {'source': source_series, 'target': target_series},
-        'output_data': {'source': source_values, 'target': target_values}  
-    }
-    
-    return rep_dict, reduced_temporal_scale, info_dict
-
-
-
-def reduce_scenario(scenario: Scenario, method: Clustermethod, include: list, \
-    scale_level: int = None, source_scenario: Scenario = None, target_scenario: Scenario= None, aspect: Union[Resource, Process] = None, \
-        location: Location = None, periods: int = None, reference_dict: dict = None) -> Scenario:
-    """reduce scenario using a particular method
-
-    Args:
-        scenario (Scenario): full-scale scenario
-        location (Location): location in scenario to cluster
-        periods (int): number of clustering intervals
-        scale_level (int): scale at which to cluster
-        method (Clustermethod): available clustering methods
-
-    Returns:
-        Scenario: reduced scenario
-    """
-
-    if method is Clustermethod.agg_hierarchial:
-        rep_dict, reduced_temporal_scale, info_dict = agg_hierarchial(scenario.scales, scale_level=scale_level, periods=periods,
-                                                                     cost_factor=scenario.cost_factor[location.name], capacity_factor=scenario.capacity_factor[location.name],
-                                                                     demand_factor = scenario.demand_factor[location.name], include = include)
-        
-
-        
-    if method is Clustermethod.dynamic_warping:
-        rep_dict, reduced_temporal_scale, info_dict = dynamic_warping(source_scenario = source_scenario, \
-            target_scenario= target_scenario, include= include, aspect= aspect, scale_level= scale_level, reference_dict = reference_dict)
-
-    
-    reduced_scenario_scaleiter = [(i) for i in product(
-        *[reduced_temporal_scale.scale[i] for i in reduced_temporal_scale.scale])]
-    
-    # cluster_wt = {scale: rep_dict[scale]['cluster_wt'] for scale in reduced_scenario_scaleiter}    
-                
-        
-    reduced_scenario = Scenario(name=f"{scenario.name}_reduced", scales=reduced_temporal_scale,
-                                network=scenario.network, expenditure_scale_level=scenario.expenditure_scale_level,
-                                scheduling_scale_level=scenario.scheduling_scale_level, network_scale_level=scenario.network_scale_level,
-                                demand_scale_level=scenario.demand_scale_level, \
-                                    label=f"{scenario.label}(reduced)")
-
-    len_ = len(list(list(scenario.cost_factor[location.name].values())[0].keys())[0])
-    reduced_scenario.cost_factor = {location.name: {i: {
-        j[:len_]: scenario.cost_factor[location.name][i][rep_dict[j]['rep_period'][:len_]] \
-            for j in list(rep_dict.keys())} for i in list(scenario.cost_factor[location.name])}}
-
-    len_ = len(list(list(scenario.capacity_factor[location.name].values())[0].keys())[0])
-    reduced_scenario.capacity_factor = {location.name: {i: {
-        j[:len_]: scenario.capacity_factor[location.name][i][rep_dict[j]['rep_period'][:len_]] \
-            for j in list(rep_dict.keys())} for i in list(scenario.capacity_factor[location.name])}}
-
-    len_ = len(list(list(scenario.demand_factor[location.name].values())[0].keys())[0])
-    reduced_scenario.demand_factor = {location.name: {i: {
-        j[:len_]: scenario.demand_factor[location.name][i][rep_dict[j]['rep_period'][:len_]] \
-            for j in list(rep_dict.keys())} for i in list(scenario.demand_factor[location.name])}}
-
-    # reduced_scenario.
-    
-    # print(cluster_wt)
-   
-    
-    if reference_dict is not None:
-        reduced_scenario.cluster_wt = {scale: reference_dict[scale]['cluster_wt'] for scale in reduced_scenario_scaleiter}
-    
-    else:    
-    
-        reduced_scenario.cluster_wt = {scale: rep_dict[scale]['cluster_wt'] for scale in reduced_scenario_scaleiter}
-
-    
-    return reduced_scenario, rep_dict, info_dict
-# %%
-
-
-# %%
diff --git a/src/energiapy/utils/data_utils.py b/src/energiapy/utils/data_utils.py
index 5982dfc0..19ecac04 100644
--- a/src/energiapy/utils/data_utils.py
+++ b/src/energiapy/utils/data_utils.py
@@ -140,15 +140,14 @@ def make_f_purchase(location_list: list, day_list: list, hour_list: list, resour
     return f_purchase_dict_
 
 
-def make_henry_price_df(file_name: str, year: int, stretch: bool) -> pandas.DataFrame:
-    """makes a df from Henry Spot Price Index data
-    Days with missing data are filled using previous day values
+def make_henry_price_df(file_name: str, year: int, stretch: bool = False,) -> pandas.DataFrame:
+    """makes a df from data with missing data filled using previous day values
     The costs are converted to $/kg from $/MMBtu using a factor of /22.4
 
     Args:
         file_name (str): provide csv file with data
         year (int): import data from a particular year
-        stretch (bool): if True, streches the timescale from days (365) to hours (8760)
+        stretch (bool): if True, streches the timescale from days (365) to hours (8760). Defaults to False.
 
 
     Returns:
@@ -289,6 +288,24 @@ def load_results(filename: str) -> Result:
     return results
 
 
+def remove_outliers(data: pandas.DataFrame, sd_cuttoff: int = 2) -> pandas.DataFrame:
+    """Removes outliers upto chosen standard deviations
+    fixes data as the mean of data around the point
+    Args:
+        data (pandas.DataFrame): input data
+        sd_cuttoff (int, optional): data upto integer number of standard deviations. Defaults to 2.
 
+    Returns:
+        pandas.DataFrame: data sans outliers
+    """
+    data_mean, data_std = data.mean(), data.std()
+    # identify outliers
+    cut_off = data_std * 3
+    lower, upper = data_mean - cut_off, data_mean + cut_off
+    for i in range(len(data)):
+        x = data.iloc[i].values[0]
+        if x < float(lower) or x > float(upper):
+            data.iloc[i] = (data.iloc[i-1] + data.iloc[i+1])/2
+    return data
 
 # %%
diff --git a/src/energiapy/utils/math_utils.py b/src/energiapy/utils/math_utils.py
index 51ddd34d..4f69c5cd 100644
--- a/src/energiapy/utils/math_utils.py
+++ b/src/energiapy/utils/math_utils.py
@@ -1,4 +1,4 @@
-#%%
+
 """Math utilities  
 """
 
@@ -29,16 +29,20 @@ def scaler(input_df: pandas.DataFrame, scale:list, child_scale:list = None) -> p
 
     cols = list(input_df.columns)
     scaled_df = pandas.DataFrame()
-
     for col in cols:
         if child_scale is not None:
-            col_names = [col + '-' + str(i) for i in range(len(child_scale))]
+            col_names = [str(col) + '-' + str(i) for i in range(len(child_scale))]
             reshaped_df = numpy.reshape(input_df[col].values, (len(scale), len(child_scale)))
         else:
             col_names = [col]
-            reshaped_df = input_df      
-            
+            # reshaped_df = input_df 
+            if len(cols) > 1: 
+                reshaped_df = numpy.reshape(input_df[col].values, (len(scale), 1))
+            else:
+                reshaped_df = input_df 
+                    
         scaler = StandardScaler().fit(reshaped_df)
+
         scaled_iter = pandas.DataFrame(scaler.transform(reshaped_df), columns=col_names)
         scaled_df = pandas.concat([scaled_df, scaled_iter], axis = 1)
     return scaled_df
@@ -81,4 +85,4 @@ def generate_connectivity_matrix(scale_len):
             connect_[i_, i_+1] = 1
     return connect_
 
-# %%
+
diff --git a/src/energiapy/utils/nsrdb_utils.py b/src/energiapy/utils/nsrdb_utils.py
index 987600dd..2f1c1a5c 100644
--- a/src/energiapy/utils/nsrdb_utils.py
+++ b/src/energiapy/utils/nsrdb_utils.py
@@ -82,6 +82,7 @@ def nearest_site(tree, latitude, longitude): # find the data point closest to la
         'daily' : 48, #averages over the day
         }
     averaged_output = pandas.DataFrame()
+    
     for attr in attrs:
         full_output = nsrdb_data[attr][:, idx] #native data set at 30 mins
         averaged_output[attr] = numpy.average(full_output.reshape(-1, timestep_dict[resolution]), axis = 1) #averages over resolution
@@ -90,5 +91,5 @@ def nearest_site(tree, latitude, longitude): # find the data point closest to la
     if save is not None:
         averaged_output.to_csv(save+ '.csv')
         
-    return lat_lon, averaged_output
+    return lat_lon, averaged_output, full_output
 
diff --git a/src/energiapy/utils/plot_utils.py b/src/energiapy/utils/plot_utils.py
index 0d04fc6f..069736ac 100644
--- a/src/energiapy/utils/plot_utils.py
+++ b/src/energiapy/utils/plot_utils.py
@@ -1,4 +1,3 @@
-#%%
 """Utilities for plots, manages axes and such
 """
 
@@ -25,10 +24,12 @@ def axis_formatter(axes, xcord:list, axis_labels:str = 'M'):
     if axis_labels == 'M' and len(xcord) == 365:
         axes.xaxis.set_major_locator(ticker.FixedLocator((pos_list_365)))
         axes.xaxis.set_major_formatter(ticker.FixedFormatter((name_list)))
+    if axis_labels == 'M' and len(xcord) == 12:
+        axes.xaxis.set_major_locator(ticker.FixedLocator((list(range(12)))))
+        axes.xaxis.set_major_formatter(ticker.FixedFormatter((name_list)))
         
         
     else:
         pass
     return axes
-        
-# %%
+                
diff --git a/src/energiapy/utils/process_utils.py b/src/energiapy/utils/process_utils.py
index 7f930fd4..ac540f73 100644
--- a/src/energiapy/utils/process_utils.py
+++ b/src/energiapy/utils/process_utils.py
@@ -23,7 +23,8 @@ def create_storage_process(process) -> Process:
         Process: Dummy process for storage
     """
     process_dummy = Process(name= process.name+'_discharge', conversion = process.conversion_discharge, prod_min= process.prod_min, \
-        prod_max= process.prod_max, introduce= process.introduce, retire= process.retire, lifetime= process.lifetime, label = process.label +'_storage')
+        prod_max= process.prod_max, introduce= process.introduce, retire= process.retire, capex = 0, vopex = 0, fopex =0,\
+            lifetime= process.lifetime, label = process.label +'_storage')
     return process_dummy
 
 


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/test_process.py b/tests/test_process.py
new file mode 100644
index 00000000..1ea355e9
--- /dev/null
+++ b/tests/test_process.py
@@ -0,0 +1,30 @@
+from src.energiapy.components.resource import Resource
+from src.energiapy.components.process import Process, ProcessMode, CostDynamics
+
+def test_process_multiconv():
+    Resource1 = Resource(name = 'resource1')
+    Resource2 = Resource(name = 'resource2')    
+    Process_multi = Process(name = 'process_multi', multiconversion= {0: {Resource1: -1, Resource2:2}, 1:{Resource1: -1, Resource2:2.5}})
+    assert Process_multi.processmode == ProcessMode.multi
+
+
+def test_process_storage_mode():
+    Resource1 = Resource(name = 'resource1')  
+    Process_storage = Process(name = 'process_storage', storage= Resource1)
+    assert Process_storage.processmode == ProcessMode.storage
+    
+def test_process_storage_storemax(): 
+    Resource1 = Resource(name = 'resource1')  
+    Process_storage = Process(name = 'process_storage', storage= Resource1, store_max= 100)
+    assert Process_storage.resource_storage.store_max == 100
+    
+   
+def test_process_cost_dynamics(): 
+    Resource1 = Resource(name = 'resource1')  
+    Process_pwl = Process(name = 'process_storage', capex= {i: i+1 for i in range(4)})
+    assert Process_pwl.cost_dynamics == CostDynamics.pwl
+    
+
+    
+     
+
diff --git a/tests/test_temporal_scale.py b/tests/test_temporal_scale.py
new file mode 100644
index 00000000..4e20ddcc
--- /dev/null
+++ b/tests/test_temporal_scale.py
@@ -0,0 +1,15 @@
+from src.energiapy.components.temporal_scale import Temporal_scale
+
+
+scales = Temporal_scale(discretization_list= [i for i in range(4)])
+
+
+def test_temporal_scale_list(scales):
+    list_ = scales.list
+    assert list_ == [i for i in range(4)]
+    
+def test_temporal_scale_scale(scales):
+    scale = scales.scale
+    assert scale == {i: list(range(i+1)) for i in range(4)}
+    
+