Regional Analysis Tutorial
Learn how to analyze OTEC potential for specific geographic regions using real oceanographic data from CMEMS.
Prerequisites
OTEX installed (
pip install otex)Internet connection for data download
HYCOM: No credentials needed (recommended for getting started)
CMEMS: Requires free Copernicus Marine account (see Installation Guide)
Overview
Regional analysis in OTEX:
Downloads temperature profiles from CMEMS or HYCOM for your region
Identifies feasible OTEC sites (adequate water depth)
Sizes plants for each site based on local conditions
Calculates LCOE considering distance to shore
Generates time-resolved power profiles
Available Regions
OTEX includes a bundled database of pre-defined regions covering tropical areas worldwide:
from otex.data import load_regions
regions = load_regions()
print(regions.head(20))
Popular regions include:
Caribbean: Jamaica, Cuba, Dominican Republic, Puerto Rico, Bahamas
Pacific: Hawaii, Philippines, Fiji, Guam, Samoa
Indian Ocean: Mauritius, Maldives, Seychelles, Reunion
Southeast Asia: Indonesia, Malaysia, Vietnam
Africa: Kenya, Tanzania, Mozambique
Basic Usage
Command Line
After installing OTEX via pip, the otex-regional command is available:
# Analyze Jamaica with default settings (136 MW, low_cost, 2020, CMEMS)
otex-regional Jamaica
# Using HYCOM data (no credentials needed)
otex-regional Jamaica --data-source HYCOM
# Specify plant size and year
otex-regional Jamaica --power -50000 --year 2021
# Use different cycle and cost assumptions
otex-regional Philippines --cycle kalina --cost high_cost
Python API
from otex.regional import run_regional_analysis
# Run analysis with HYCOM (no credentials needed)
otec_plants, sites_df = run_regional_analysis(
studied_region='Jamaica',
p_gross=-50000, # 50 MW
cost_level='low_cost',
year_start=2020,
year_end=2020,
cycle_type='rankine_closed',
fluid_type='ammonia',
use_coolprop=True,
data_source='HYCOM', # or 'CMEMS' (default)
)
Step-by-Step Guide
Step 1: Choose Your Region
First, verify your region exists in the database:
from otex.data import load_regions
regions = load_regions()
print(regions[regions['region'].str.contains('Jam', case=False)])
Output:
region north east south west demand
Jamaica 19.358 -74.009 14.083 -80.833 3.092992
Step 2: Check Available Sites
View potential OTEC sites in your region:
from otex.data import load_sites
sites = load_sites()
jamaica_sites = sites[sites['region'] == 'Jamaica']
print(f"Total sites in Jamaica: {len(jamaica_sites)}")
print(f"Water depth range: {jamaica_sites['water_depth'].min():.0f} to {jamaica_sites['water_depth'].max():.0f} m")
print(f"Distance to shore: {jamaica_sites.iloc[:, 4].min():.1f} to {jamaica_sites.iloc[:, 4].max():.1f} km")
Step 3: Run the Analysis
from otex.regional import run_regional_analysis
otec_plants, sites_df = run_regional_analysis(
studied_region='Jamaica',
p_gross=-50000,
year_start=2020,
year_end=2020,
)
This will:
Download temperature data from CMEMS or HYCOM (~5-15 minutes first time)
Process and cache data locally
Run OTEC sizing for all valid sites
Calculate LCOE for each site
Save results to
Data_Results/Jamaica/
Step 4: Examine Results
import pandas as pd
import matplotlib.pyplot as plt
# Load results
results = pd.read_csv(
'Data_Results/Jamaica/Jamaica_2020_50.0_MW_low_cost/OTEC_sites_Jamaica_2020_50.0_MW_low_cost.csv',
sep=';',
index_col='id'
)
print(results.head())
print(f"\nNumber of feasible sites: {len(results)}")
print(f"LCOE range: {results['LCOE'].min():.2f} - {results['LCOE'].max():.2f} ct/kWh")
print(f"Best site LCOE: {results['LCOE'].min():.2f} ct/kWh")
Step 5: Visualize Results
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# LCOE map
ax1 = axes[0, 0]
scatter = ax1.scatter(
results['longitude'],
results['latitude'],
c=results['LCOE'],
cmap='RdYlGn_r',
s=50
)
plt.colorbar(scatter, ax=ax1, label='LCOE (ct/kWh)')
ax1.set_xlabel('Longitude')
ax1.set_ylabel('Latitude')
ax1.set_title('LCOE by Location')
# LCOE histogram
ax2 = axes[0, 1]
ax2.hist(results['LCOE'], bins=20, edgecolor='white')
ax2.axvline(results['LCOE'].median(), color='red', linestyle='--', label='Median')
ax2.set_xlabel('LCOE (ct/kWh)')
ax2.set_ylabel('Number of sites')
ax2.set_title('LCOE Distribution')
ax2.legend()
# Net power vs LCOE
ax3 = axes[1, 0]
ax3.scatter(results['p_net_nom'], results['LCOE'], alpha=0.6)
ax3.set_xlabel('Net Power (MW)')
ax3.set_ylabel('LCOE (ct/kWh)')
ax3.set_title('Net Power vs LCOE')
# Temperature difference
ax4 = axes[1, 1]
delta_T = results['T_WW_med'] - results['T_CW_med']
ax4.scatter(delta_T, results['LCOE'], alpha=0.6)
ax4.set_xlabel('Temperature Difference (°C)')
ax4.set_ylabel('LCOE (ct/kWh)')
ax4.set_title('ΔT vs LCOE')
plt.tight_layout()
plt.savefig('jamaica_analysis.png', dpi=150)
plt.show()
Understanding the Output Files
OTEC Sites CSV
Column |
Description |
Unit |
Single-yr |
Multi-yr |
|---|---|---|---|---|
id |
Site identifier |
- |
✓ |
✓ |
longitude |
Site longitude |
degrees |
✓ |
✓ |
latitude |
Site latitude |
degrees |
✓ |
✓ |
p_net_nom |
Nominal net power |
MW |
✓ |
✓ |
AEP |
Lifetime-average annual energy |
MWh |
✓ |
✓ |
CAPEX |
Capital expenditure |
$M |
✓ |
✓ |
LCOE |
Levelized cost of energy |
ct/kWh |
✓ |
✓ |
|
Legacy single-rate CRF LCOE for comparison |
ct/kWh |
— |
✓ |
|
Minimum yearly AEP across the run window |
MWh |
— |
✓ |
|
Median yearly AEP |
MWh |
— |
✓ |
|
Maximum yearly AEP |
MWh |
— |
✓ |
|
Standard deviation of yearly AEP |
MWh |
— |
✓ |
Configuration |
Optimal ΔT configuration |
- |
✓ |
✓ |
T_WW_min/med/max |
Warm water temperature stats |
°C |
✓ |
✓ |
T_CW_min/med/max |
Cold water temperature stats |
°C |
✓ |
✓ |
For multi-year runs, a companion CSV OTEC_sites_yearly_*.csv is also
emitted with one row per (site, year) and columns id, year,
p_net_mean_kW, AEP_MWh. Useful for boxplots of inter-annual
variability or for fitting trend lines.
Power Profiles CSV
Daily average net power output over the year:
profiles = pd.read_csv(
'Data_Results/Jamaica/.../net_power_profiles_per_day_Jamaica_2020_50.0_MW_low_cost.csv',
sep=';',
index_col=0,
parse_dates=True
)
# Plot annual profile
profiles.plot(figsize=(12, 4))
plt.ylabel('Net Power (kW)')
plt.title('Average Daily Net Power Output')
plt.show()
Choosing a Data Source
OTEX supports two oceanographic data sources. Choose based on your needs:
Feature |
CMEMS |
HYCOM |
|---|---|---|
Authentication |
Required (free account) |
Not required |
Temporal coverage |
1993–present |
1994–2015, 2019–2024 |
Spatial resolution |
0.083° (~9 km) |
0.08° (~9 km) |
Depth levels |
50 |
40 |
Data gap |
None |
2016–2018 |
Recommendation: Use HYCOM for quick analyses and getting started. Use CMEMS for years outside HYCOM coverage or when continuous multi-year time series are needed.
# Compare results from both sources
otec_hycom, sites_hycom = run_regional_analysis(
studied_region='Jamaica', year_start=2020, year_end=2020,
data_source='HYCOM'
)
otec_cmems, sites_cmems = run_regional_analysis(
studied_region='Jamaica', year_start=2020, year_end=2020,
data_source='CMEMS'
)
Advanced Options
Custom Plant Size
from otex.regional import run_regional_analysis
# Analyze different plant sizes
for size_mw in [20, 50, 100, 200]:
run_regional_analysis(
studied_region='Jamaica',
p_gross=-size_mw * 1000,
year_start=2020,
year_end=2020
)
Different Thermodynamic Cycles
from otex.regional import run_regional_analysis
# Compare cycles
cycles = ['rankine_closed', 'kalina', 'uehara']
for cycle in cycles:
run_regional_analysis(
studied_region='Jamaica',
p_gross=-50000,
cycle_type=cycle
)
Multi-Year Analysis
Since 0.2.0, multi-year simulations are supported natively. Pass an inclusive year range and the pipeline reads N NetCDFs (one per year), concatenates them along the time axis, and recomputes LCOE using a discounted-cashflow NPV formulation that accounts for leap years, configurable degradation, and OPEX escalation.
# Single CLI invocation covering 2018-2021
otex-regional Jamaica --year-start 2018 --year-end 2021
from otex import run_regional_analysis
run_regional_analysis(
studied_region='Jamaica',
year_start=2018,
year_end=2021,
)
For a multi-year run the output OTEC_sites_*.csv adds inter-annual
variability columns (AEP_min, AEP_p50, AEP_max, AEP_std) and a
LCOE_legacy column for comparison with the single-year formulation.
A second CSV OTEC_sites_yearly_*.csv reports per-(site, year) energy.
To run analyses with independent yearly snapshots (the legacy workflow, no NPV), keep the loop:
for year in 2018 2019 2020 2021; do
otex-regional Jamaica --year $year
done
NPV LCOE — degradation and OPEX escalation
When n_years > 1, LCOE is computed as the per-year discounted cashflow:
CAPEX + Σ_t OPEX_t / (1+r)^t
LCOE = 100 ¢ × ─────────────────────────────────
Σ_t E_t / (1+r)^t
where the sum runs over the full project lifetime (Economics.lifetime_years,
30 years by default). Years outside the simulated window are filled by
cyclically replicating the simulated pattern. Two configurable
multipliers are applied to each year:
Power degradation (lowers
E_t). Three models, configurable onEconomics.degradation:Model
Formula
Defaults
constant(1 - rate)^trate = 0.005(0.5 %/yr)logistic1 - L / (1 + exp(-k(t - t0)))L=0.30, k=0.30, t0=15stepdiscrete drops at scheduled years
years=[10, 20], drops=[0.05, 0.05]OPEX escalation (raises
OPEX_t). Three models, configurable onEconomics.opex_escalation:Model
Formula
Defaults
flatconstant
—
fixed_rate(1 + rate)^trate = 0.0indexeduser-supplied vector of length
lifetime_years—
Example with custom degradation and 2 % OPEX escalation:
from otex.config import OTEXConfig, Economics
from otex.economics import DegradationConfig, OpexEscalationConfig
config = OTEXConfig()
config.economics = Economics(
lifetime_years=30,
discount_rate=0.08,
degradation=DegradationConfig(model='logistic',
logistic_L=0.20,
logistic_k=0.25,
logistic_t0=12),
opex_escalation=OpexEscalationConfig(model='fixed_rate', rate=0.02),
)
inputs = config.to_legacy_dict()
# pass `inputs` to run_regional_analysis via parameters_and_constants(...)
Single-year runs (n_years == 1) preserve the legacy single-rate CRF
LCOE for backward compatibility — they ignore the degradation and
escalation config.
Combining with Uncertainty Analysis
After regional analysis, run uncertainty analysis on the best site:
import pandas as pd
from otex.analysis import MonteCarloAnalysis, UncertaintyConfig
# Load regional results
results = pd.read_csv('...OTEC_sites_Jamaica_2020_50.0_MW_low_cost.csv', sep=';')
# Find best site
best_site = results.loc[results['LCOE'].idxmin()]
print(f"Best site: ({best_site['longitude']}, {best_site['latitude']})")
print(f"T_WW: {best_site['T_WW_med']:.1f}°C, T_CW: {best_site['T_CW_med']:.1f}°C")
# Run uncertainty analysis for this site
config = UncertaintyConfig(n_samples=500, seed=42)
mc = MonteCarloAnalysis(
T_WW=best_site['T_WW_med'],
T_CW=best_site['T_CW_med'],
config=config,
p_gross=-50000
)
ua_results = mc.run()
stats = ua_results.compute_statistics()
print(f"\nLCOE with uncertainty:")
print(f"Mean: {stats['lcoe']['lcoe_mean']:.2f} ct/kWh")
print(f"90% CI: [{stats['lcoe']['lcoe_p5']:.2f}, {stats['lcoe']['lcoe_p95']:.2f}]")
Performance Tips
First run is slower: Data download and processing takes 5-15 minutes
Subsequent runs are faster: Processed data is cached in HDF5 files
Reduce memory usage: Use smaller regions or reduce spatial resolution
Parallel processing: Enabled by default for Monte Carlo
Troubleshooting
“No valid sites found”
Check that your region has sufficient water depth:
from otex.data import load_sites
sites = load_sites()
region_sites = sites[sites['region'] == 'YourRegion']
print(f"Depths: {region_sites['water_depth'].describe()}")
Sites need water depth of at least 600-1000m.
Download failures
CMEMS:
Verify credentials:
copernicusmarine login --checkCheck internet connection
Try again later (CMEMS servers may be busy)
Try HYCOM as an alternative:
data_source='HYCOM'
HYCOM:
Verify the year is within coverage (1994–2015 or 2019–2024)
HYCOM OPeNDAP servers may be temporarily unavailable — retry later
Check internet connection
Memory errors
Reduce plant size or use a smaller region.