# Adding a Technology

REopt can be used in many ways, but its primary use is to evaluate the techno-economic feasibility of energy generation and storage technologies. In this section we describe how one might add a new technology to the REopt model for evaluation. At a high level the steps are:

- Define the mathematical model for how the technology will interact with the other technologies, which includes:
- defining appropriate decision variables (the technology's capacity for example)
- defining model constraints (operational constraints for example)

- Define the inputs and default values necessary to model the technology in the mathematical model
- Map the input values to the sets and coefficients needed in the mathematical model
- Create an adapter function to output the desired results from the mathematical model
- Test the new technology

All steps are not necessarily executed in this order and in fact most likely must be done in concert. For example, in order to define a model constraint one must define the input parameters. Also, it is good practice to think of how you will test the new technology from the very beginning of the design process and incrementally test your additions to the model as well as make sure that no existing tests fail due to your modifications to REopt.

## 1. Mathematical Model

Each technology will have unique decision variables and constraints. However, there are some decision variables that apply to many technologies. We will use the `PV`

technology to demonstrate some variables and constraints that are shared among all generation technologies and some that are unique to `PV`

.

First, the `PV`

technology can meet electrical demand and thus is part of the `techs.elec`

. By including the `PV`

technology in the set of `techs.elec`

we can take advantage of existing model constraints such as the electrical load balance:

```
@constraint(m, [ts in p.time_steps_with_grid],
sum(p.production_factor[t, ts] * p.levelization_factor[t] * m[Symbol("dvRatedProduction"*_n)][t,ts] for t in p.techs.elec)
+ sum( m[Symbol("dvDischargeFromStorage"*_n)][b,ts] for b in p.s.storage.types.elec )
+ sum(m[Symbol("dvGridPurchase"*_n)][ts, tier] for tier in 1:p.s.electric_tariff.n_energy_tiers)
==
sum( sum(m[Symbol("dvProductionToStorage"*_n)][b, t, ts] for b in p.s.storage.types.elec)
+ m[Symbol("dvCurtail"*_n)][t, ts] for t in p.techs.elec)
+ sum(m[Symbol("dvGridToStorage"*_n)][b, ts] for b in p.s.storage.types.elec)
+ p.s.electric_load.loads_kw[ts]
)
```

Throughout the REopt code `p`

is used as the variable name for the concrete instance of `REoptInputs`

. The `p`

is a legacy name from when the `REoptInputs`

was called a **p**arameter structure. Also, `m`

is used throughout the code for the JuMP Model and `p.s`

is the `Scenario`

structure.

From the load balance constraint we can see that the `PV`

technology (and each `techs.elec`

) includes input parameters for the `production_factor`

and `levelization_factor`

, and that the `PV`

technology has the decision variables `dvRatedProduction`

and `dvCurtail`

.

All decision variables in the model start with `dv`

and use camel case naming after `dv`

. Also, in order to take advantage of dynamic variable names for multinode models we use the `Symbol`

notation (e.g. `m[Symbol("dvProductionToStorage"*_n)`

) to define and access variables in the model.

The `p.techs`

data structure is defined as follows:

### Techs

`REopt.Techs`

— Type`Techs`

`Techs`

contains the index sets that are used to define the model constraints and decision variables.

```
mutable struct Techs
all::Vector{String}
elec::Vector{String}
pv::Vector{String}
gen::Vector{String}
pbi::Vector{String}
no_curtail::Vector{String}
no_turndown::Vector{String}
segmented::Vector{String}
heating::Vector{String}
cooling::Vector{String}
boiler::Vector{String}
fuel_burning::Vector{String}
thermal::Vector{String}
chp::Vector{String}
requiring_oper_res::Vector{String}
providing_oper_res::Vector{String}
electric_chiller::Vector{String}
absorption_chiller::Vector{String}
steam_turbine::Vector{String}
can_supply_steam_turbine::Vector{String}
electric_heater::Vector{String}
can_serve_space_heating::Vector{String}
can_serve_dhw::Vector{String}
can_serve_process_heat::Vector{String}
ghp::Vector{String}
ashp::Vector{String}
end
```

`REopt.Techs`

— Method`Techs(s::Scenario)`

Create a `Techs`

struct for the REoptInputs.

`REopt.Techs`

— Method`Techs(p::REoptInputs, s::BAUScenario)`

Create a `Techs`

struct for the BAUInputs

From the Techs definition we can see that there are already a lot of different energy generation technology categories in REopt. Adding a new technology to the model could be as simple as adding the appropriate inputs to `REoptInputs`

(described in the next section) and using the `Techs`

structure to define which variables and constraints apply to the new technology.

The `PV`

technology is also part of a unique set of `Techs`

, namely the `techs.pv`

(there can be multiple `PV`

technologies in a single model as we will see soon). An example of a constraint applied over `techs.pv`

is:

```
@constraint(m, [loc in p.pvlocations],
sum(m[Symbol("dvSize"*_n)][t] * p.pv_to_location[t][loc] for t in p.techs.pv) <= p.maxsize_pv_locations[loc]
)
```

Here we can see that the `dvSize`

for each `techs.pv`

is constrained based on the location of each `PV`

technology. This constraint allows us to uniquely limit the `PV`

capacity for roof mounted systems vs. ground mounted systems based on the available space at a site. We also see some additional inputs for the `PV`

technology, such as the `pvlocations`

and `maxsize_pv_locations`

. Creating these input values is described in the next two sections.

## 2. User Inputs

Any new technology should have a `technologyname.jl`

file in the `src/core`

directory. For example, in the `src/core/pv.jl`

file we have a data structure and constructor for defining default values and creating the `PV`

structure that is attached to the Scenario. Once the new technology's data structure is defined it must be added to the `Scenario`

structure (see `src/core/scenario.jl`

).

When adding a new technology to REopt one must decide on how a user of the REopt will define the technology. Continuing with the `PV`

example we saw that we need to define the `production_factor`

for the `PV`

technology in every time step. The `production_factor`

varies from zero to one and defines the availability of the technology. For `PV`

we have a default method for creating the `production_factor`

as well as allow the user to provide their own `production_factor`

.

We let the user define the `production_factor`

by providing the `PV`

s `production_factor_series`

input in their JSON file or dictionary when creating their Scenario. If the user does not provide a value for `production_factor_series`

then we use the PVWatts API to get a `production_factor`

based on the `Site.latitude`

and `Site.longitude`

. The PV inputs structure also allows the user to change the arguments that are passed to PVWatts.

## 3. REopt Inputs

The REoptInputs constructor is the work-horse for defining all the mathematical model parameters. It converts the user's Scenario into a format that is necessary for adding the model decision variables and constraints.

A major part of the REoptInputs constructor is creating arrays that are indexed on sets of strings (defined in Techs) that allow us to define constraints all applicable technologies. Continuing with the `PV`

example, the electrical load balance constraint includes:

`sum(p.production_factor[t, ts] * p.levelization_factor[t] * m[Symbol("dvRatedProduction"*_n)][t,ts] for t in p.techs.elec) `

which implies that we need to define a `production_factor`

for all `techs.elec`

in every time step `ts`

. To create the `production_factor`

array the REoptInputs constructor first creates an empty array like so:

`production_factor = DenseAxisArray{Float64}(undef, techs.all, 1:length(s.electric_load.loads_kw))`

and then passes that array to technology specific functions that add their production factors to the `production_factor`

array. For example, for `PV`

within the `setup_pv_inputs`

method we have:

```
for pv in s.pvs
production_factor[pv.name, :] = get_production_factor(pv, s.site.latitude, s.site.longitude)
...
end
```

The completed `production_factor`

array is then attached to the `REoptInputs`

structure so that it can be used as needed to create the mathematical model.

## 4. Results

After adding a new technology to the REopt mathematical model and getting the new inputs set up you can create some results from the optimized model. Some or all of your new results can also be used in a test for the new technology.

All of the results methods are defined in `src/results`

, with `src/results/results.jl`

containing the main method for creating results. The results are returned to the user as a dictionary. If a user is not modeling your new technology then there is no reason to create any new results. Therefore, in `reopt_results`

we have:

```
if !isempty(p.techs.pv)
add_pv_results(m, p, d; _n)
end
```

which uses the model `m`

and the `REoptInputs`

`p`

to add results to the dictionary `d`

.

The `_n`

argument is used in many places in REopt to optionally modeled multinode scenarios. The default value for `_n`

is an empty string. When modeling multiple nodes the `n`

in the `_n`

string is set to the `Site.node`

value, which is an integer. For example, if the `Site.node`

is `3`

then `_n = "_3"`

.

## 5. Testing the new technology

Adding a new test is not necessarily the last step in adding a technology to the REopt model. In fact, it is best to use a simple test to test your code as you add the new technolgy and then adapt the test as you add more capability to the code. For example, once you have created you new technology's input interface you can test just creating a `Scenario`

with the new technology by passing the path to a JSON file that contains the minimum required inputs for a Scenario and your new technology. This might look like:

```
@testset "My new technology" begin
s = Scenario("path/to/mynewtech.json")
end
```

The next testing step might be checking the `REoptInputs`

additions for your new technolgy:

```
@testset "My new technology" begin
s = Scenario("path/to/mynewtech.json")
p = REoptInputs(s)
end
```

Once you have all of your new inputs set up you can test the model creation with:

```
@testset "My new technology" begin
m = Model(Cbc.Optimizer)
build_reopt!(m, "path/to/mynewtech.json")
end
```

Finally, you can test the full work-flow with something like:

```
@testset "My new technology" begin
m = Model(Cbc.Optimizer)
results = run_reopt(m, "path/to/mynewtech.json")
@test results["mynewtech"]["some_result"] ≈ 78.9 atol=0.1
end
```