Skip to content

Commit

Permalink
Merge pull request #11 from idaholab/develop
Browse files Browse the repository at this point in the history
Merge in Develop into Main branch (v.1.1)
  • Loading branch information
manoj1511 authored Apr 14, 2023
2 parents ea3e8ff + a5c4b46 commit 2c6ed55
Show file tree
Hide file tree
Showing 11 changed files with 239 additions and 23 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ outputs/*.csv

CMakeSettings.json
.vs/
.vscode/
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ endif()
# Find pybind11
find_package(pybind11 CONFIG)

# Make sure C++11 flag is set for macOS.
if(APPLE)
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang\$")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
endif()
endif()

# Update submodules
find_package(Git QUIET)
if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
Expand Down
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,51 @@ Caldera Grid has the following requirements to be able to compile on windows
build -> Install Grid
```


##### On Ubuntu Linux

```
First, installed Ubuntu.
sudo apt inatall git
mkdir ~/Documents/dev
Checked out the repos, put in ~/Documents/dev
sudo apt install cmake
sudo apt install build-essential
cd ~/Documents/
wget https://repo.anaconda.com/miniconda/Miniconda3-py39_4.12.0-Linux-x86_64.sh
bash Miniconda3-py39_4.12.0-Linux-x86_64.sh
(installed anaconda)
(then restarted the terminal)
conda create -n caldera python=3.7
conda activate caldera
pip install helics
conda install pandas numpy scipy cvxopt
pip install cython
pip install 'OpenDSSDirect.py[extras]'
pip install "pybind11[global]"
cd Caldera_Grid
git switch develop
mkdir build
cd build
cmake -DPROJECT=eMosaic -DICM=ON ../
make -j 4
make install
```

##### Notes for macOS
```
To install anaconda:
-------
brew install --cask anaconda
source /usr/local/anaconda3/bin/activate
conda create -n caldera python=3.7
conda activate caldera
```

#### Running Caldera Grid

1. Open Anaconda prompt
Expand Down
2 changes: 1 addition & 1 deletion inputs/parameters/VS100.csv
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Key,Value,Description
target_P3_reference__percent_of_maxP3,90,
max_delta_kW_per_min,1000,
volt_delta_kW_curve,0.95|-40 ; 0.99|-2 ; 1|0 ; 1.03|0 ; 1.05|10,V1|deltaP1 ; V2|deltaP2 ; V3|deltaP3 ; V4|deltaP4 Vn|deltaPn
volt_delta_kW_curve,0.95|-40 ; 0.99|-2 ; 1|0 ; 1.03|0 ; 1.05|10,V1|deltaP1 ; V2|deltaP2 ; V3|deltaP3 ; V4|deltaP4 ; Vn|deltaPn
voltage_LPF,is_active|True ; seed|40 ; window_size_LB|2 ; window_size_UB|18 ; window_type|Rectangular,
2 changes: 1 addition & 1 deletion inputs/parameters/VS200-A.csv
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Key,Value,Description
target_P3_reference__percent_of_maxP3,70,
max_delta_kVAR_per_min,1000,
volt_var_curve,0.95|-100 ; 0.975|-25 ; 0.99|-5 ; 1|0 ; 1.03|0 ; 1.05|20,V1|Q1 ; V2|Q2 ; V3|Q3 ; V4|Q4 Vn|Qn
volt_var_curve,0.95|-100 ; 0.975|-25 ; 0.99|-5 ; 1|0 ; 1.03|0 ; 1.05|20,V1|Q1 ; V2|Q2 ; V3|Q3 ; V4|Q4 ; Vn|Qn
voltage_LPF,is_active|True ; seed|35 ; window_size_LB|2 ; window_size_UB|18 ; window_type|Rectangular,
can_provide_reactive_power_after_battery_full,FALSE,
2 changes: 1 addition & 1 deletion inputs/parameters/VS200-B.csv
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Key,Value,Description
target_P3_reference__percent_of_maxP3,70,
max_delta_kVAR_per_min,0.25,
volt_var_curve,0.95|-90 ; 0.98|0 ; 1.02|0 ; 1.05|90,V1|Q1 ; V2|Q2 ; V3|Q3 ; V4|Q4 Vn|Qn
volt_var_curve,0.95|-90 ; 0.98|0 ; 1.02|0 ; 1.05|90,V1|Q1 ; V2|Q2 ; V3|Q3 ; V4|Q4 ; Vn|Qn
voltage_LPF,is_active|True ; seed|35 ; window_size_LB|25 ; window_size_UB|30 ; window_type|Rectangular,
can_provide_reactive_power_after_battery_full,TRUE,
2 changes: 1 addition & 1 deletion inputs/parameters/VS200-C.csv
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Key,Value,Description
target_P3_reference__percent_of_maxP3,70,
max_delta_kVAR_per_min,0.25,
volt_var_curve,0.95|-90 ; 0.98|0 ; 1.02|0 ; 1.05|90,V1|Q1 ; V2|Q2 ; V3|Q3 ; V4|Q4 Vn|Qn
volt_var_curve,0.95|-90 ; 0.98|0 ; 1.02|0 ; 1.05|90,V1|Q1 ; V2|Q2 ; V3|Q3 ; V4|Q4 ; Vn|Qn
voltage_LPF,is_active|True ; seed|35 ; window_size_LB|20 ; window_size_UB|30 ; window_type|Rectangular,
can_provide_reactive_power_after_battery_full,TRUE,
189 changes: 175 additions & 14 deletions source/base/OpenDSS_aux.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,75 @@

import opendssdirect as dss
import os, math
import pandas as pd

from global_aux import OpenDSS_message_types, input_datasets, non_pev_feeder_load


class open_dss:

# NOTE: The 'open_dss' class does two things: Use OpenDSS, and do logging.
# if the boolean 'use_opendss' is false, then this class will just do the logging.
# TODO: Separate the logging into a separate federate.
def __init__(self, base_dir, use_opendss):

if use_opendss == True:
self.helper = open_dss_helper(base_dir)
else:
self.helper = logger_helper(base_dir)


def get_input_dataset_enum_list(self):
return self.helper.get_request_list()


def load_input_datasets(self, datasets_dict):
self.helper.load_input_datasets(datasets_dict)


def initialize(self):
return self.helper.initialize()


def process_control_messages(self, simulation_unix_time, message_dict):
return self.helper.process_control_messages(simulation_unix_time, message_dict)


def set_caldera_pev_charging_loads(self, node_pevPQ):
self.helper.set_caldera_pev_charging_loads(node_pevPQ)


def get_pu_node_voltages_for_caldera(self):
return self.helper.get_pu_node_voltages_for_caldera()


def solve(self, simulation_unix_time):
self.helper.solve(simulation_unix_time)


def log_data(self, simulation_unix_time):
self.helper.log_data(simulation_unix_time)


def post_simulation(self):
self.helper.post_simulation()


class open_dss_helper:

def __init__(self, base_dir):
self.base_dir = base_dir
self.dss_file_name = 'ieee34.dss'


def get_input_dataset_enum_list(self):
def get_request_list(self):
return [input_datasets.baseLD_data_obj, input_datasets.all_caldera_node_names, input_datasets.HPSE_caldera_node_names]


def load_input_datasets(self, datasets_dict):
# datasets_dict is a dictionary with input_datasets as keys.
self.datasets_dict = datasets_dict

def initialize(self):

def initialize(self):
baseLD_data_obj = self.datasets_dict[input_datasets.baseLD_data_obj]
all_caldera_node_names = self.datasets_dict[input_datasets.all_caldera_node_names]
HPSE_caldera_node_names = self.datasets_dict[input_datasets.HPSE_caldera_node_names]
Expand All @@ -46,29 +94,83 @@ def initialize(self):
self.dss_logger = open_dss_logger_A(self.base_dir, all_caldera_node_names, HPSE_caldera_node_names)

return is_successful



def process_control_messages(self, simulation_unix_time, message_dict):
return self.dss_external_control.process_control_messages(simulation_unix_time, message_dict)


def set_caldera_pev_charging_loads(self, node_pevPQ):
self.node_pevPQ = node_pevPQ
self.dss_Caldera.set_caldera_pev_charging_loads(node_pevPQ)



def get_pu_node_voltages_for_caldera(self):
return self.dss_Caldera.get_pu_node_voltages_for_caldera()



def solve(self, simulation_unix_time):
self.dss_core.solve(simulation_unix_time)



def log_data(self, simulation_unix_time):
self.dss_logger.log_data(simulation_unix_time, self.node_pevPQ)

def post_simulation(self):
pass

class logger_helper:

def __init__(self, base_dir):
self.base_dir = base_dir

def get_request_list(self):
return [input_datasets.baseLD_data_obj, input_datasets.all_caldera_node_names]

def load_input_datasets(self, datasets_dict):
# datasets_dict is a dictionary with input_datasets as keys.
self.datasets_dict = datasets_dict

def initialize(self):

self.baseLD_data_obj = self.datasets_dict[input_datasets.baseLD_data_obj]
self.all_caldera_node_names = self.datasets_dict[input_datasets.all_caldera_node_names]

self.logger_obj = logger(self.base_dir, self.baseLD_data_obj, self.all_caldera_node_names)

is_successful = True
return is_successful

def process_control_messages(self, simulation_unix_time, message_dict):

return_dict = {}

for (msg_enum, parameters) in message_dict.items():
if msg_enum == OpenDSS_message_types.get_all_node_voltages:
return_dict[msg_enum] = self.get_pu_node_voltages_for_caldera()

else:
raise ValueError('Invalid message in caldera_ICM_aux::process_message.')

# The return value (return_dict) must be a dictionary with OpenDSS_message_types as keys.
# If there is nothing to return, return an empty dictionary.
return return_dict


def set_caldera_pev_charging_loads(self, node_pevPQ):
self.node_pevPQ = node_pevPQ

def get_pu_node_voltages_for_caldera(self):
return_dict = {}
for node_name in self.all_caldera_node_names:
return_dict[node_name] = 1.0

return return_dict

def solve(self, simulation_unix_time):
self.logger_obj.compute_total_load_profiles(self.node_pevPQ, simulation_unix_time)

def log_data(self, simulation_unix_time):
return None

def post_simulation(self):
self.logger_obj.write_data_to_disk()


class open_dss_external_control:

Expand Down Expand Up @@ -380,4 +482,63 @@ def log_data(self, simulation_unix_time, node_pevPQ):
node_V = node_V/len(X)

tmp_str = '{}, {}, {}, {}'.format(simulation_time_hrs, node_V, pevQ_kVAR, pevP_kW)
f_node.write(tmp_str + '\n')
f_node.write(tmp_str + '\n')

class logger:

def __init__(self, base_dir, baseLD_data_obj, all_caldera_node_names):

self.base_dir = base_dir
self.all_caldera_node_names = all_caldera_node_names
self.baseLD_data_obj = baseLD_data_obj
#print("all_caldera_node_names : {}".format(all_caldera_node_names))

self.real_power_profiles = {}
self.reactive_power_profiles = {}
self.real_power_profiles["simulation_time_hrs"] = []
self.real_power_profiles["base_load_kW"] = []
self.reactive_power_profiles["simulation_time_hrs"] = []
self.reactive_power_profiles["base_load_kW"] = []

for node_name in all_caldera_node_names:
self.real_power_profiles[node_name] = []
self.reactive_power_profiles[node_name] = []


def compute_total_load_profiles(self, node_pevPQ, simulation_unix_time):

simulation_time_hrs = simulation_unix_time/3600.0

index = math.floor((simulation_unix_time - self.baseLD_data_obj.data_start_unix_time) / self.baseLD_data_obj.data_timestep_sec)

if (index < 0) or (index >= len(self.baseLD_data_obj.actual_load_akW)):
print("Error : base_LD index computed not in data range")
exit()

base_LD_kW = self.baseLD_data_obj.actual_load_akW[index]
self.real_power_profiles["simulation_time_hrs"].append(simulation_time_hrs)
self.real_power_profiles["base_load_kW"].append(base_LD_kW)

self.reactive_power_profiles["simulation_time_hrs"].append(simulation_time_hrs)
self.reactive_power_profiles["base_load_kW"].append(base_LD_kW)

for (node_name, (P_kW, Q_kVAR)) in node_pevPQ.items():
self.real_power_profiles[node_name].append(P_kW)
self.reactive_power_profiles[node_name].append(Q_kVAR)

def write_data_to_disk(self):
Output_dir = self.base_dir + "/outputs/"

df = pd.DataFrame(self.real_power_profiles)
df.to_csv(Output_dir + "real_power_profiles.csv", index=False)

df = pd.DataFrame(self.reactive_power_profiles)
df.to_csv(Output_dir + "reactive_power_profiles.csv", index=False)

def get_pu_node_voltages_for_caldera(self):

return_dict = {}
for node_name in self.all_caldera_node_names:
return_dict[node_name] = 1.0

return return_dict
7 changes: 4 additions & 3 deletions source/federates/OpenDSS_federate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from OpenDSS_aux import open_dss
from Helics_Helper import send, receive, cleanup

def open_dss_federate(base_dir, json_config_file_name, simulation_time_constraints):
def open_dss_federate(base_dir, json_config_file_name, simulation_time_constraints, use_opendss):

print_communication = False
#=====================================
Expand Down Expand Up @@ -61,7 +61,7 @@ def open_dss_federate(base_dir, json_config_file_name, simulation_time_constrain
#=====================================
# Initialize OpenDSS
#=====================================
dss_obj = open_dss(base_dir)
dss_obj = open_dss(base_dir, use_opendss)

#-------------------------------------
# Get Information from Load Input Files
Expand Down Expand Up @@ -157,7 +157,6 @@ def open_dss_federate(base_dir, json_config_file_name, simulation_time_constrain
if len(msg_dict) != 0:
send(msg_dict, typeB_control_endpoint, source)


#=====================================
# Sub Step 4
#=====================================
Expand All @@ -183,6 +182,8 @@ def open_dss_federate(base_dir, json_config_file_name, simulation_time_constrain
if federate_time >= end_simulation_unix_time:
break

dss_obj.post_simulation()

#=====================================
# Terminate Federate
#=====================================
Expand Down
3 changes: 2 additions & 1 deletion start_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
end_simulation_unix_time = 25*3600

ensure_pev_charge_needs_met_for_ext_control_strategy = False
use_opendss = False

#---------------------

Expand Down Expand Up @@ -109,7 +110,7 @@

# OpenDSS Federate
json_config_file_name = 'OpenDSS.json'
p = Process(target=open_dss_federate, args=(base_dir, json_config_file_name, simulation_time_constraints,), name="open_dss_federate")
p = Process(target=open_dss_federate, args=(base_dir, json_config_file_name, simulation_time_constraints, use_opendss,), name="open_dss_federate")
processes.append(p)

#---------------------------
Expand Down

0 comments on commit 2c6ed55

Please sign in to comment.