Learn how to analyze OTEC potential for specific geographic regions using real oceanographic data from CMEMS.
- 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)
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
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
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_costfrom 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)
)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
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")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/
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")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()| 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 | ✓ | ✓ |
LCOE_legacy |
Legacy single-rate CRF LCOE for comparison | ct/kWh | — | ✓ |
AEP_min |
Minimum yearly AEP across the run window | MWh | — | ✓ |
AEP_p50 |
Median yearly AEP | MWh | — | ✓ |
AEP_max |
Maximum yearly AEP | MWh | — | ✓ |
AEP_std |
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.
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()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'
)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
)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
)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 2021from 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
doneWhen 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.
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}]")- 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
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.
CMEMS:
- Verify credentials:
copernicusmarine login --check - Check 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
Reduce plant size or use a smaller region.