From fcdd6ff9ebbcedfe70583751e1507f0f31960190 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 01:36:32 +0000 Subject: [PATCH 1/7] Initial plan From 0dfacc61e9097747936e67b4c7005c6d903f1390 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 01:39:52 +0000 Subject: [PATCH 2/7] Add comprehensive README and example Jupyter notebook Co-authored-by: SBFRF <8375832+SBFRF@users.noreply.github.com> --- README.md | 253 +++++++++++--- examples/pyDIWASP_example.ipynb | 599 ++++++++++++++++++++++++++++++++ 2 files changed, 803 insertions(+), 49 deletions(-) create mode 100644 examples/pyDIWASP_example.ipynb diff --git a/README.md b/README.md index 815e9ad..aab21aa 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,207 @@ -# pyDiwasp -conversion of diwasp package (DIWASP: DIrectional WAve SPectrum analysis Version 1.4) for python -converted from https://github.com/metocean/diwasp - -I would LOVE help making this into better package of the original diwasp tool. Please check issues for needed functionality adds. - -## Toolbox contents: -### Main functions: -- dirspec.m Main function for directional wave analysis -- readspec.m Reads in DIWASP format spectrum files -- writespec.m Writes DIWASP format spectrum files -- plotspec.m Plots DIWASP spectrums -- testspec.m Testing function for the estimation methods -- makespec.m Makes a fake spectrum and generates fake data for testing dirspec.m -- infospec.m Returns information about a directional spectrum -- data_structures.m is a help file describing the new Version 1.1 data structures - -## Private functions (some can be used as stand alone functions): -### The transfer functions -- /private/elev.m -- /private/pres.m -- /private/velx.m -- /private/vely.m -- /private/velz.m -- /private/slpx.m -- /private/slpy.m -- /private/vels.m -- /private/accs.m - -### The estimation functions -- /private/DFTM.m -- /private/EMLM.m -- /private/IMLM.m -- /private/EMEP.m -- /private/BDM.m - -### Miscellaneous functions -- /private/smoothspec.m -- /private/wavenumber.m -- /private/makerandomsea.m -- /private/makewavedata.m -- /private/Hsig.m -- /private/gsamp.m -- /private/check_data.m - - -carying original license agreement and copyright - -## License agreement +# pyDIWASP + +Python conversion of the DIWASP package (DIrectional WAve SPectrum analysis Version 1.4) + +pyDIWASP is a Python implementation of the DIWASP toolbox for estimating directional wave spectra from wave measurement data. It provides functions to analyze wave measurements from arrays of instruments (e.g., pressure sensors, current meters, wave gauges) and compute the directional distribution of wave energy. + +Converted from: https://github.com/metocean/diwasp + +## Features + +- **Directional Wave Analysis**: Estimate directional wave spectra from instrument array data +- **Multiple Estimation Methods**: Supports IMLM, EMEP, EMLM, DFTM, and BDM methods +- **Flexible Input**: Works with various instrument types (pressure, velocity, elevation sensors) +- **Visualization**: Built-in plotting functions for 3D and polar spectral plots +- **Wave Statistics**: Calculate significant wave height, peak period, and dominant direction + +## Installation + +### Basic Installation + +```bash +# Clone the repository +git clone https://github.com/SBFRF/pyDIWASP.git +cd pyDIWASP + +# Install dependencies +pip install numpy scipy matplotlib +``` + +### Requirements + +- Python 3.6+ +- NumPy +- SciPy +- Matplotlib + +## Quick Start + +```python +import numpy as np +from dirspec import dirspec + +# Define instrument data structure +ID = { + 'layout': np.array([[0, 10, 20], [0, 0, 0], [0, 0, 0]]), # x, y, z positions + 'datatypes': ['pres', 'pres', 'pres'], # instrument types + 'depth': 10.0, # water depth in meters + 'fs': 2.0, # sampling frequency in Hz + 'data': wave_data # nsamples x ninstruments array +} + +# Define spectral matrix structure +SM = { + 'freqs': np.linspace(0.05, 0.5, 50), # frequency bins in Hz + 'dirs': np.linspace(-np.pi, np.pi, 36) # direction bins in radians +} + +# Define estimation parameters +EP = { + 'method': 'IMLM', # estimation method + 'iter': 100 # number of iterations +} + +# Compute directional spectrum +SMout, EPout = dirspec(ID, SM, EP) +``` + +For more detailed examples, see the [example notebook](examples/pyDIWASP_example.ipynb). + +## Main Functions + +### dirspec.py +Main function for directional wave spectrum analysis. + +**Usage:** +```python +SMout, EPout = dirspec(ID, SM, EP, Options) +``` + +**Parameters:** +- `ID`: Instrument data structure (dict) +- `SM`: Spectral matrix structure (dict) +- `EP`: Estimation parameters structure (dict) +- `Options`: Optional parameters (list of key-value pairs) + +**Returns:** +- `SMout`: Output spectral matrix with computed directional spectrum +- `EPout`: Estimation parameters with actual values used + +### infospec.py +Calculates and displays information about a directional spectrum. + +**Returns:** Significant wave height (Hsig), peak period (Tp), direction of peak period (DTp), and dominant direction (Dp). + +### plotspec.py +Plots the spectral matrix in 3D or polar form. + +**Plot types:** +1. 3D surface plot +2. Polar plot +3. 3D surface plot (compass bearings) +4. Polar plot (compass bearings) + +### writespec.py +Writes directional spectrum data to file in DIWASP format. + +### interpspec.py +Interpolates a spectrum onto a different frequency/direction grid. + +## Private Functions + +The `private/` directory contains internal functions used by the main analysis routines: + +### Transfer Functions +Calculate instrument response to waves: +- `elev.py` - Surface elevation transfer function +- `pres.py` - Pressure sensor transfer function +- `velx.py`, `vely.py` - Horizontal velocity transfer functions +- `wavenumber.py` - Dispersion relation solver + +### Estimation Methods +Directional spectrum estimation algorithms: +- `IMLM.py` - Iterated Maximum Likelihood Method (default) +- `EMEP.py` - Extended Maximum Entropy Principle +- `EMLM.py` - Extended Maximum Likelihood Method +- (Additional methods: DFTM, BDM) + +### Utility Functions +- `smoothspec.py` - Spectral smoothing +- `hsig.py` - Significant wave height calculation +- `check_data.py` - Data structure validation +- `diwasp_csd.py` - Cross-spectral density estimation +- `spectobasis.py` - Spectral basis conversion + +## Data Structures + +### Instrument Data Structure (ID) +Dictionary with the following fields: +- `layout`: 3×N array of instrument positions [x; y; z] in meters +- `datatypes`: List of instrument types ('pres', 'elev', 'velx', 'vely', etc.) +- `depth`: Water depth in meters +- `fs`: Sampling frequency in Hz +- `data`: N_samples × N_instruments array of measurements + +### Spectral Matrix Structure (SM) +Dictionary with the following fields: +- `freqs`: Array of frequency values +- `dirs`: Array of direction values (radians or degrees) +- `S`: Frequency × Direction array of spectral density (output only) +- `funit`: Frequency unit ('Hz' or 'rad/s') +- `dunit`: Direction unit ('rad' or 'deg') +- `xaxisdir`: Reference axis direction (default: 90 = East) + +### Estimation Parameters Structure (EP) +Dictionary with the following fields: +- `method`: Estimation method ('IMLM', 'EMEP', 'EMLM', 'DFTM', 'BDM') +- `nfft`: FFT length (auto-calculated if not specified) +- `dres`: Directional resolution (default: 180) +- `iter`: Number of iterations for iterative methods (default: 100) +- `smooth`: Spectral smoothing ('ON' or 'OFF', default: 'ON') + +## Estimation Methods + +### IMLM (Iterated Maximum Likelihood Method) +- **Default method** +- Iteratively refines directional spectrum estimate +- Good balance of accuracy and computational efficiency +- Recommended for most applications + +### EMEP (Extended Maximum Entropy Principle) +- Based on maximum entropy principle +- Works well for narrow directional spreads +- Suitable for swell-dominated conditions + +### Other Methods +- **EMLM**: Extended Maximum Likelihood Method +- **DFTM**: Direct Fourier Transform Method +- **BDM**: Bayesian Directional Method + +## Examples + +See the [examples directory](examples/) for Jupyter notebooks demonstrating: +- Basic directional spectrum estimation +- Working with different instrument configurations +- Comparing estimation methods +- Visualization options + +## Contributing + +Contributions are welcome! Please feel free to submit pull requests or open issues for: +- Bug fixes +- Documentation improvements +- New features +- Additional examples + +## References + +All implemented calculation algorithms are described in: + +Hashimoto, N. (1997). "Analysis of the directional wave spectrum from field data". +In: *Advances in Coastal Engineering Vol. 3*. Ed: Liu, P.L-F. +Pub: World Scientific, Singapore. + +## Original Copyright and License DIWASP, is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. However, the DIWASP license includes the following addendum concerning its usage: diff --git a/examples/pyDIWASP_example.ipynb b/examples/pyDIWASP_example.ipynb new file mode 100644 index 0000000..a947706 --- /dev/null +++ b/examples/pyDIWASP_example.ipynb @@ -0,0 +1,599 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# pyDIWASP Example: Directional Wave Spectrum Analysis\n", + "\n", + "This notebook demonstrates how to use pyDIWASP to analyze directional wave spectra from instrument array data.\n", + "\n", + "## Overview\n", + "\n", + "pyDIWASP is a Python implementation of the DIWASP (DIrectional WAve SPectrum) toolbox. It estimates the directional distribution of wave energy from measurements by an array of wave instruments.\n", + "\n", + "### What is a Directional Wave Spectrum?\n", + "\n", + "A directional wave spectrum describes how wave energy is distributed across:\n", + "- **Frequencies** (or wave periods)\n", + "- **Directions** (where waves are coming from)\n", + "\n", + "This is more informative than a 1D frequency spectrum because it tells us not just how much energy is at each frequency, but also which direction that energy is traveling." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "First, import the necessary libraries:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# Add parent directory to path to import pyDIWASP modules\n", + "sys.path.insert(0, '..')\n", + "\n", + "from dirspec import dirspec\n", + "from infospec import infospec\n", + "from plotspec import plotspec\n", + "\n", + "# Set random seed for reproducibility\n", + "np.random.seed(42)\n", + "\n", + "print(\"Libraries imported successfully!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example 1: Basic Directional Spectrum Analysis\n", + "\n", + "In this example, we'll create synthetic wave data representing a wave field with:\n", + "- A dominant wave direction from the east (90 degrees)\n", + "- Peak period around 10 seconds (0.1 Hz)\n", + "- Wave height around 2 meters\n", + "\n", + "We'll use a simple 3-sensor array to measure pressure at different locations." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 1: Generate Synthetic Wave Data\n", + "\n", + "We'll create a simple wave field using superposition of sinusoidal components:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Simulation parameters\n", + "duration = 1024 # seconds\n", + "fs = 2.0 # sampling frequency (Hz)\n", + "depth = 10.0 # water depth (m)\n", + "\n", + "# Time array\n", + "t = np.arange(0, duration, 1/fs)\n", + "nsamples = len(t)\n", + "\n", + "# Wave parameters\n", + "peak_freq = 0.1 # Hz (10 second period)\n", + "peak_dir = np.pi/2 # radians (from East)\n", + "wave_amplitude = 1.0 # meters\n", + "\n", + "# Instrument array layout (3 pressure sensors in an L-shape)\n", + "# Layout is 3xN array: [x positions; y positions; z positions]\n", + "layout = np.array([\n", + " [0, 10, 0], # x positions (meters)\n", + " [0, 0, 10], # y positions (meters)\n", + " [-depth, -depth, -depth] # z positions (meters, negative = below surface)\n", + "])\n", + "\n", + "ninst = layout.shape[1]\n", + "\n", + "print(f\"Simulation setup:\")\n", + "print(f\" Duration: {duration} seconds\")\n", + "print(f\" Sampling rate: {fs} Hz\")\n", + "print(f\" Number of samples: {nsamples}\")\n", + "print(f\" Number of instruments: {ninst}\")\n", + "print(f\" Water depth: {depth} m\")\n", + "print(f\"\\nInstrument positions (x, y, z):\")\n", + "for i in range(ninst):\n", + " print(f\" Sensor {i+1}: ({layout[0,i]:.1f}, {layout[1,i]:.1f}, {layout[2,i]:.1f}) m\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Generate synthetic wave data\n", + "# Simple model: superposition of wave components\n", + "\n", + "# Create several wave components with different frequencies and directions\n", + "frequencies = np.array([0.08, 0.10, 0.12, 0.15]) # Hz\n", + "directions = np.array([np.pi/2, np.pi/2, np.pi/2 + np.pi/6, np.pi/3]) # radians\n", + "amplitudes = np.array([0.5, 1.0, 0.3, 0.2]) # meters\n", + "\n", + "# Calculate wavenumbers using dispersion relation: omega^2 = g*k*tanh(k*h)\n", + "g = 9.81 # gravity (m/s^2)\n", + "wavenumbers = []\n", + "for f in frequencies:\n", + " omega = 2 * np.pi * f\n", + " # Solve iteratively for k\n", + " k = omega**2 / g # deep water approximation as initial guess\n", + " for _ in range(10):\n", + " k = omega**2 / (g * np.tanh(k * depth))\n", + " wavenumbers.append(k)\n", + "wavenumbers = np.array(wavenumbers)\n", + "\n", + "# Initialize data array\n", + "data = np.zeros((nsamples, ninst))\n", + "\n", + "# Generate pressure signal at each instrument location\n", + "for i in range(len(frequencies)):\n", + " f = frequencies[i]\n", + " theta = directions[i]\n", + " A = amplitudes[i]\n", + " k = wavenumbers[i]\n", + " omega = 2 * np.pi * f\n", + " \n", + " # Phase at each instrument location\n", + " for j in range(ninst):\n", + " x = layout[0, j]\n", + " y = layout[1, j]\n", + " z = layout[2, j]\n", + " \n", + " # Spatial phase\n", + " phase_xy = k * (x * np.cos(theta) + y * np.sin(theta))\n", + " \n", + " # Pressure response (includes depth attenuation)\n", + " pressure_response = np.cosh(k * (z + depth)) / np.cosh(k * depth)\n", + " \n", + " # Add wave component\n", + " random_phase = np.random.uniform(0, 2*np.pi)\n", + " data[:, j] += A * pressure_response * np.cos(omega * t - phase_xy + random_phase)\n", + "\n", + "# Add some noise\n", + "noise_level = 0.05 # 5% noise\n", + "data += noise_level * np.random.randn(nsamples, ninst)\n", + "\n", + "print(f\"\\nGenerated wave data shape: {data.shape}\")\n", + "print(f\"Data range: [{data.min():.2f}, {data.max():.2f}] (pressure units)\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visualize the Generated Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Plot time series from each instrument\n", + "fig, axes = plt.subplots(ninst, 1, figsize=(12, 6), sharex=True)\n", + "for i in range(ninst):\n", + " axes[i].plot(t[:500], data[:500, i])\n", + " axes[i].set_ylabel(f'Sensor {i+1}\\n(pressure)')\n", + " axes[i].grid(True, alpha=0.3)\n", + "\n", + "axes[-1].set_xlabel('Time (seconds)')\n", + "fig.suptitle('Synthetic Wave Measurements (first 250 seconds)', fontsize=14)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 2: Set Up the Instrument Data Structure\n", + "\n", + "The Instrument Data (ID) structure contains all information about the measurement array:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create instrument data structure\n", + "ID = {\n", + " 'layout': layout,\n", + " 'datatypes': ['pres', 'pres', 'pres'], # all pressure sensors\n", + " 'depth': depth,\n", + " 'fs': fs,\n", + " 'data': data\n", + "}\n", + "\n", + "print(\"Instrument Data (ID) structure created:\")\n", + "print(f\" Layout shape: {ID['layout'].shape}\")\n", + "print(f\" Data types: {ID['datatypes']}\")\n", + "print(f\" Depth: {ID['depth']} m\")\n", + "print(f\" Sampling frequency: {ID['fs']} Hz\")\n", + "print(f\" Data shape: {ID['data'].shape}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 3: Define the Spectral Matrix Structure\n", + "\n", + "The Spectral Matrix (SM) structure defines the frequency and directional resolution of the output:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Define output spectral matrix\n", + "SM = {\n", + " 'freqs': np.linspace(0.05, 0.3, 30), # frequency bins (Hz)\n", + " 'dirs': np.linspace(-np.pi, np.pi, 36) # direction bins (radians)\n", + "}\n", + "\n", + "print(\"Spectral Matrix (SM) structure created:\")\n", + "print(f\" Frequency range: {SM['freqs'][0]:.3f} - {SM['freqs'][-1]:.3f} Hz\")\n", + "print(f\" Number of frequency bins: {len(SM['freqs'])}\")\n", + "print(f\" Direction range: {SM['dirs'][0]:.2f} - {SM['dirs'][-1]:.2f} radians\")\n", + "print(f\" Number of direction bins: {len(SM['dirs'])}\")\n", + "print(f\" Directional resolution: {np.degrees(SM['dirs'][1] - SM['dirs'][0]):.1f} degrees\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 4: Set Estimation Parameters\n", + "\n", + "The Estimation Parameters (EP) structure controls the analysis method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Define estimation parameters\n", + "EP = {\n", + " 'method': 'IMLM', # Iterated Maximum Likelihood Method\n", + " 'iter': 100, # number of iterations\n", + " 'smooth': 'ON' # enable spectral smoothing\n", + "}\n", + "\n", + "print(\"Estimation Parameters (EP) structure created:\")\n", + "print(f\" Method: {EP['method']}\")\n", + "print(f\" Iterations: {EP['iter']}\")\n", + "print(f\" Smoothing: {EP['smooth']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 5: Compute the Directional Spectrum\n", + "\n", + "Now we can run the main analysis function:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Compute directional spectrum\n", + "print(\"Computing directional spectrum...\")\n", + "print(\"=\" * 50)\n", + "\n", + "# Set plot type to 0 (no automatic plotting) so we can customize\n", + "options = ['PLOTTYPE', 0]\n", + "\n", + "SMout, EPout = dirspec(ID, SM, EP, options)\n", + "\n", + "print(\"=\" * 50)\n", + "print(\"\\nDirectional spectrum computed successfully!\")\n", + "print(f\"Output spectrum shape: {SMout['S'].shape}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 6: Analyze Results\n", + "\n", + "Use `infospec` to get key wave statistics:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get wave information\n", + "Hsig, Tp, DTp, Dp = infospec(SMout)\n", + "\n", + "print(\"\\n\" + \"=\"*50)\n", + "print(\"WAVE ANALYSIS RESULTS\")\n", + "print(\"=\"*50)\n", + "print(f\"Significant Wave Height (Hsig): {Hsig:.2f} m\")\n", + "print(f\"Peak Period (Tp): {Tp:.2f} s\")\n", + "print(f\"Direction at Peak (DTp): {DTp:.2f} rad = {np.degrees(DTp):.1f}°\")\n", + "print(f\"Dominant Direction (Dp): {Dp:.2f} rad = {np.degrees(Dp):.1f}°\")\n", + "print(\"=\"*50)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 7: Visualize the Directional Spectrum\n", + "\n", + "Create custom visualizations of the results:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create a 1D frequency spectrum (integrated over directions)\n", + "freq_spectrum = np.sum(np.real(SMout['S']), axis=1) * (SMout['dirs'][1] - SMout['dirs'][0])\n", + "\n", + "# Create figure with subplots\n", + "fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n", + "\n", + "# Plot 1D frequency spectrum\n", + "axes[0].plot(SMout['freqs'], freq_spectrum, 'b-', linewidth=2)\n", + "axes[0].axvline(1/Tp, color='r', linestyle='--', label=f'Peak Period = {Tp:.1f}s')\n", + "axes[0].set_xlabel('Frequency (Hz)', fontsize=12)\n", + "axes[0].set_ylabel('Spectral Density (m²/Hz)', fontsize=12)\n", + "axes[0].set_title('Frequency Spectrum (Directionally Integrated)', fontsize=13)\n", + "axes[0].grid(True, alpha=0.3)\n", + "axes[0].legend()\n", + "\n", + "# Plot 1D directional distribution (integrated over frequencies)\n", + "dir_spectrum = np.sum(np.real(SMout['S']), axis=0) * (SMout['freqs'][1] - SMout['freqs'][0])\n", + "axes[1].plot(np.degrees(SMout['dirs']), dir_spectrum, 'g-', linewidth=2)\n", + "axes[1].axvline(np.degrees(Dp), color='r', linestyle='--', label=f'Dominant Dir = {np.degrees(Dp):.1f}°')\n", + "axes[1].set_xlabel('Direction (degrees)', fontsize=12)\n", + "axes[1].set_ylabel('Spectral Density (m²/degree)', fontsize=12)\n", + "axes[1].set_title('Directional Distribution (Frequency Integrated)', fontsize=13)\n", + "axes[1].grid(True, alpha=0.3)\n", + "axes[1].legend()\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create 2D contour plot of directional spectrum\n", + "fig, ax = plt.subplots(figsize=(10, 8))\n", + "\n", + "# Convert directions to degrees for better readability\n", + "dirs_deg = np.degrees(SMout['dirs'])\n", + "\n", + "# Create meshgrid\n", + "F, D = np.meshgrid(SMout['freqs'], dirs_deg)\n", + "\n", + "# Plot filled contours\n", + "levels = 20\n", + "contourf = ax.contourf(F, D, np.real(SMout['S']).T, levels=levels, cmap='viridis')\n", + "contour = ax.contour(F, D, np.real(SMout['S']).T, levels=levels, colors='k', alpha=0.3, linewidths=0.5)\n", + "\n", + "# Add colorbar\n", + "cbar = plt.colorbar(contourf, ax=ax, label='Spectral Density (m²s/deg)')\n", + "\n", + "# Mark peak\n", + "ax.plot(1/Tp, np.degrees(DTp), 'r*', markersize=15, label=f'Peak: T={Tp:.1f}s, θ={np.degrees(DTp):.1f}°')\n", + "\n", + "ax.set_xlabel('Frequency (Hz)', fontsize=12)\n", + "ax.set_ylabel('Direction (degrees)', fontsize=12)\n", + "ax.set_title('2D Directional Wave Spectrum', fontsize=14, fontweight='bold')\n", + "ax.legend(loc='upper right')\n", + "ax.grid(True, alpha=0.3)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 8: Use Built-in Plotting Functions\n", + "\n", + "pyDIWASP includes built-in plotting functions for standard visualizations:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 3D surface plot\n", + "plotspec(SMout, ptype=1)\n", + "plt.title('3D Surface Plot - Cartesian Directions', fontsize=14, fontweight='bold')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Polar contour plot\n", + "plotspec(SMout, ptype=2)\n", + "plt.title('Polar Contour Plot - Cartesian Directions', fontsize=14, fontweight='bold')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example 2: Comparing Different Estimation Methods\n", + "\n", + "pyDIWASP supports several estimation methods. Let's compare two common ones:\n", + "- **IMLM**: Iterated Maximum Likelihood Method (default)\n", + "- **EMEP**: Extended Maximum Entropy Principle" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Compute spectrum using EMEP method\n", + "EP_emep = {\n", + " 'method': 'EMEP',\n", + " 'iter': 100,\n", + " 'smooth': 'ON'\n", + "}\n", + "\n", + "print(\"Computing directional spectrum using EMEP method...\")\n", + "print(\"=\" * 50)\n", + "SMout_emep, _ = dirspec(ID, SM, EP_emep, ['PLOTTYPE', 0])\n", + "print(\"=\" * 50)\n", + "print(\"Complete!\\n\")\n", + "\n", + "# Get statistics for EMEP\n", + "Hsig_emep, Tp_emep, DTp_emep, Dp_emep = infospec(SMout_emep)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Compare the two methods\n", + "fig, axes = plt.subplots(1, 2, figsize=(14, 6))\n", + "\n", + "# IMLM method\n", + "F, D = np.meshgrid(SMout['freqs'], np.degrees(SMout['dirs']))\n", + "c1 = axes[0].contourf(F, D, np.real(SMout['S']).T, levels=15, cmap='viridis')\n", + "axes[0].plot(1/Tp, np.degrees(DTp), 'r*', markersize=12, label=f'Peak')\n", + "axes[0].set_xlabel('Frequency (Hz)')\n", + "axes[0].set_ylabel('Direction (degrees)')\n", + "axes[0].set_title(f'IMLM Method\\nHsig={Hsig:.2f}m, Tp={Tp:.1f}s', fontweight='bold')\n", + "plt.colorbar(c1, ax=axes[0], label='Density')\n", + "axes[0].legend()\n", + "axes[0].grid(True, alpha=0.3)\n", + "\n", + "# EMEP method\n", + "c2 = axes[1].contourf(F, D, np.real(SMout_emep['S']).T, levels=15, cmap='viridis')\n", + "axes[1].plot(1/Tp_emep, np.degrees(DTp_emep), 'r*', markersize=12, label=f'Peak')\n", + "axes[1].set_xlabel('Frequency (Hz)')\n", + "axes[1].set_ylabel('Direction (degrees)')\n", + "axes[1].set_title(f'EMEP Method\\nHsig={Hsig_emep:.2f}m, Tp={Tp_emep:.1f}s', fontweight='bold')\n", + "plt.colorbar(c2, ax=axes[1], label='Density')\n", + "axes[1].legend()\n", + "axes[1].grid(True, alpha=0.3)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()\n", + "\n", + "print(\"\\nComparison of Results:\")\n", + "print(\"=\" * 50)\n", + "print(f\"{'Parameter':<20} {'IMLM':<15} {'EMEP':<15}\")\n", + "print(\"=\" * 50)\n", + "print(f\"{'Hsig (m)':<20} {Hsig:<15.2f} {Hsig_emep:<15.2f}\")\n", + "print(f\"{'Tp (s)':<20} {Tp:<15.2f} {Tp_emep:<15.2f}\")\n", + "print(f\"{'DTp (degrees)':<20} {np.degrees(DTp):<15.1f} {np.degrees(DTp_emep):<15.1f}\")\n", + "print(f\"{'Dp (degrees)':<20} {np.degrees(Dp):<15.1f} {np.degrees(Dp_emep):<15.1f}\")\n", + "print(\"=\" * 50)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "This notebook demonstrated:\n", + "\n", + "1. **Setting up input data structures** (ID, SM, EP)\n", + "2. **Running directional spectrum analysis** using `dirspec()`\n", + "3. **Extracting wave statistics** using `infospec()`\n", + "4. **Visualizing results** with both custom plots and built-in functions\n", + "5. **Comparing different estimation methods** (IMLM vs EMEP)\n", + "\n", + "### Key Takeaways\n", + "\n", + "- pyDIWASP requires three main input structures: ID (instrument data), SM (spectral matrix), and EP (estimation parameters)\n", + "- The IMLM method is the default and works well for most applications\n", + "- Different estimation methods may give slightly different results\n", + "- The output includes both the full 2D spectrum and integrated statistics\n", + "\n", + "### Next Steps\n", + "\n", + "To use pyDIWASP with your own data:\n", + "1. Load your wave measurement data\n", + "2. Set up the ID structure with your instrument configuration\n", + "3. Define appropriate frequency and direction ranges in SM\n", + "4. Choose an estimation method in EP\n", + "5. Run `dirspec()` and analyze the results\n", + "\n", + "For more information, see the [pyDIWASP README](../README.md)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From d84a6e892b3954ef915923a773ebe247b8f219d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 01:42:00 +0000 Subject: [PATCH 3/7] Fix nfft handling bug in dirspec Co-authored-by: SBFRF <8375832+SBFRF@users.noreply.github.com> --- __pycache__/dirspec.cpython-312.pyc | Bin 0 -> 8718 bytes __pycache__/infospec.cpython-312.pyc | Bin 0 -> 2792 bytes __pycache__/interpspec.cpython-312.pyc | Bin 0 -> 2740 bytes __pycache__/plotspec.cpython-312.pyc | Bin 0 -> 4784 bytes __pycache__/writespec.cpython-312.pyc | Bin 0 -> 1645 bytes dirspec.py | 2 +- examples/test_notebook.py | 461 ++++++++++++++++++ private/__pycache__/EMEP.cpython-312.pyc | Bin 0 -> 11441 bytes private/__pycache__/IMLM.cpython-312.pyc | Bin 0 -> 3567 bytes .../__pycache__/check_data.cpython-312.pyc | Bin 0 -> 5476 bytes .../__pycache__/diwasp_csd.cpython-312.pyc | Bin 0 -> 2438 bytes private/__pycache__/elev.cpython-312.pyc | Bin 0 -> 483 bytes private/__pycache__/hsig.cpython-312.pyc | Bin 0 -> 1031 bytes private/__pycache__/pres.cpython-312.pyc | Bin 0 -> 687 bytes .../__pycache__/smoothspec.cpython-312.pyc | Bin 0 -> 1869 bytes .../__pycache__/spectobasis.cpython-312.pyc | Bin 0 -> 1582 bytes private/__pycache__/velx.cpython-312.pyc | Bin 0 -> 648 bytes private/__pycache__/vely.cpython-312.pyc | Bin 0 -> 646 bytes .../__pycache__/wavenumber.cpython-312.pyc | Bin 0 -> 1508 bytes 19 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 __pycache__/dirspec.cpython-312.pyc create mode 100644 __pycache__/infospec.cpython-312.pyc create mode 100644 __pycache__/interpspec.cpython-312.pyc create mode 100644 __pycache__/plotspec.cpython-312.pyc create mode 100644 __pycache__/writespec.cpython-312.pyc create mode 100644 examples/test_notebook.py create mode 100644 private/__pycache__/EMEP.cpython-312.pyc create mode 100644 private/__pycache__/IMLM.cpython-312.pyc create mode 100644 private/__pycache__/check_data.cpython-312.pyc create mode 100644 private/__pycache__/diwasp_csd.cpython-312.pyc create mode 100644 private/__pycache__/elev.cpython-312.pyc create mode 100644 private/__pycache__/hsig.cpython-312.pyc create mode 100644 private/__pycache__/pres.cpython-312.pyc create mode 100644 private/__pycache__/smoothspec.cpython-312.pyc create mode 100644 private/__pycache__/spectobasis.cpython-312.pyc create mode 100644 private/__pycache__/velx.cpython-312.pyc create mode 100644 private/__pycache__/vely.cpython-312.pyc create mode 100644 private/__pycache__/wavenumber.cpython-312.pyc diff --git a/__pycache__/dirspec.cpython-312.pyc b/__pycache__/dirspec.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af3b3ab4f53edb7454978c4923d539117e41ef91 GIT binary patch literal 8718 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 literal 0 HcmV?d00001 diff --git a/dirspec.py b/dirspec.py index a968fed..86c7276 100644 --- a/dirspec.py +++ b/dirspec.py @@ -89,7 +89,7 @@ def dirspec(ID, SM, EP, Options_=None): ndat, szd = np.shape(ID['data']) #get resolution of FFT - if not specified, calculate a sensible value - if 'nfft' not in EP: + if 'nfft' not in EP or not EP['nfft']: nfft = int(2 ** (8 + np.round(np.log2(ID['fs'])))) EP['nfft'] = nfft else: diff --git a/examples/test_notebook.py b/examples/test_notebook.py new file mode 100644 index 0000000..a19fa10 --- /dev/null +++ b/examples/test_notebook.py @@ -0,0 +1,461 @@ +#!/usr/bin/env python +# coding: utf-8 + +# # pyDIWASP Example: Directional Wave Spectrum Analysis +# +# This notebook demonstrates how to use pyDIWASP to analyze directional wave spectra from instrument array data. +# +# ## Overview +# +# pyDIWASP is a Python implementation of the DIWASP (DIrectional WAve SPectrum) toolbox. It estimates the directional distribution of wave energy from measurements by an array of wave instruments. +# +# ### What is a Directional Wave Spectrum? +# +# A directional wave spectrum describes how wave energy is distributed across: +# - **Frequencies** (or wave periods) +# - **Directions** (where waves are coming from) +# +# This is more informative than a 1D frequency spectrum because it tells us not just how much energy is at each frequency, but also which direction that energy is traveling. + +# ## Setup +# +# First, import the necessary libraries: + +# In[ ]: + + +import sys +import numpy as np +import matplotlib.pyplot as plt + +# Add parent directory to path to import pyDIWASP modules +sys.path.insert(0, '..') + +from dirspec import dirspec +from infospec import infospec +from plotspec import plotspec + +# Set random seed for reproducibility +np.random.seed(42) + +print("Libraries imported successfully!") + + +# ## Example 1: Basic Directional Spectrum Analysis +# +# In this example, we'll create synthetic wave data representing a wave field with: +# - A dominant wave direction from the east (90 degrees) +# - Peak period around 10 seconds (0.1 Hz) +# - Wave height around 2 meters +# +# We'll use a simple 3-sensor array to measure pressure at different locations. + +# ### Step 1: Generate Synthetic Wave Data +# +# We'll create a simple wave field using superposition of sinusoidal components: + +# In[ ]: + + +# Simulation parameters +duration = 1024 # seconds +fs = 2.0 # sampling frequency (Hz) +depth = 10.0 # water depth (m) + +# Time array +t = np.arange(0, duration, 1/fs) +nsamples = len(t) + +# Wave parameters +peak_freq = 0.1 # Hz (10 second period) +peak_dir = np.pi/2 # radians (from East) +wave_amplitude = 1.0 # meters + +# Instrument array layout (3 pressure sensors in an L-shape) +# Layout is 3xN array: [x positions; y positions; z positions] +layout = np.array([ + [0, 10, 0], # x positions (meters) + [0, 0, 10], # y positions (meters) + [-depth, -depth, -depth] # z positions (meters, negative = below surface) +]) + +ninst = layout.shape[1] + +print(f"Simulation setup:") +print(f" Duration: {duration} seconds") +print(f" Sampling rate: {fs} Hz") +print(f" Number of samples: {nsamples}") +print(f" Number of instruments: {ninst}") +print(f" Water depth: {depth} m") +print(f"\nInstrument positions (x, y, z):") +for i in range(ninst): + print(f" Sensor {i+1}: ({layout[0,i]:.1f}, {layout[1,i]:.1f}, {layout[2,i]:.1f}) m") + + +# In[ ]: + + +# Generate synthetic wave data +# Simple model: superposition of wave components + +# Create several wave components with different frequencies and directions +frequencies = np.array([0.08, 0.10, 0.12, 0.15]) # Hz +directions = np.array([np.pi/2, np.pi/2, np.pi/2 + np.pi/6, np.pi/3]) # radians +amplitudes = np.array([0.5, 1.0, 0.3, 0.2]) # meters + +# Calculate wavenumbers using dispersion relation: omega^2 = g*k*tanh(k*h) +g = 9.81 # gravity (m/s^2) +wavenumbers = [] +for f in frequencies: + omega = 2 * np.pi * f + # Solve iteratively for k + k = omega**2 / g # deep water approximation as initial guess + for _ in range(10): + k = omega**2 / (g * np.tanh(k * depth)) + wavenumbers.append(k) +wavenumbers = np.array(wavenumbers) + +# Initialize data array +data = np.zeros((nsamples, ninst)) + +# Generate pressure signal at each instrument location +for i in range(len(frequencies)): + f = frequencies[i] + theta = directions[i] + A = amplitudes[i] + k = wavenumbers[i] + omega = 2 * np.pi * f + + # Phase at each instrument location + for j in range(ninst): + x = layout[0, j] + y = layout[1, j] + z = layout[2, j] + + # Spatial phase + phase_xy = k * (x * np.cos(theta) + y * np.sin(theta)) + + # Pressure response (includes depth attenuation) + pressure_response = np.cosh(k * (z + depth)) / np.cosh(k * depth) + + # Add wave component + random_phase = np.random.uniform(0, 2*np.pi) + data[:, j] += A * pressure_response * np.cos(omega * t - phase_xy + random_phase) + +# Add some noise +noise_level = 0.05 # 5% noise +data += noise_level * np.random.randn(nsamples, ninst) + +print(f"\nGenerated wave data shape: {data.shape}") +print(f"Data range: [{data.min():.2f}, {data.max():.2f}] (pressure units)") + + +# ### Visualize the Generated Data + +# In[ ]: + + +# Plot time series from each instrument +fig, axes = plt.subplots(ninst, 1, figsize=(12, 6), sharex=True) +for i in range(ninst): + axes[i].plot(t[:500], data[:500, i]) + axes[i].set_ylabel(f'Sensor {i+1}\n(pressure)') + axes[i].grid(True, alpha=0.3) + +axes[-1].set_xlabel('Time (seconds)') +fig.suptitle('Synthetic Wave Measurements (first 250 seconds)', fontsize=14) +plt.tight_layout() +plt.show() + + +# ### Step 2: Set Up the Instrument Data Structure +# +# The Instrument Data (ID) structure contains all information about the measurement array: + +# In[ ]: + + +# Create instrument data structure +ID = { + 'layout': layout, + 'datatypes': ['pres', 'pres', 'pres'], # all pressure sensors + 'depth': depth, + 'fs': fs, + 'data': data +} + +print("Instrument Data (ID) structure created:") +print(f" Layout shape: {ID['layout'].shape}") +print(f" Data types: {ID['datatypes']}") +print(f" Depth: {ID['depth']} m") +print(f" Sampling frequency: {ID['fs']} Hz") +print(f" Data shape: {ID['data'].shape}") + + +# ### Step 3: Define the Spectral Matrix Structure +# +# The Spectral Matrix (SM) structure defines the frequency and directional resolution of the output: + +# In[ ]: + + +# Define output spectral matrix +SM = { + 'freqs': np.linspace(0.05, 0.3, 30), # frequency bins (Hz) + 'dirs': np.linspace(-np.pi, np.pi, 36) # direction bins (radians) +} + +print("Spectral Matrix (SM) structure created:") +print(f" Frequency range: {SM['freqs'][0]:.3f} - {SM['freqs'][-1]:.3f} Hz") +print(f" Number of frequency bins: {len(SM['freqs'])}") +print(f" Direction range: {SM['dirs'][0]:.2f} - {SM['dirs'][-1]:.2f} radians") +print(f" Number of direction bins: {len(SM['dirs'])}") +print(f" Directional resolution: {np.degrees(SM['dirs'][1] - SM['dirs'][0]):.1f} degrees") + + +# ### Step 4: Set Estimation Parameters +# +# The Estimation Parameters (EP) structure controls the analysis method: + +# In[ ]: + + +# Define estimation parameters +EP = { + 'method': 'IMLM', # Iterated Maximum Likelihood Method + 'iter': 100, # number of iterations + 'smooth': 'ON' # enable spectral smoothing +} + +print("Estimation Parameters (EP) structure created:") +print(f" Method: {EP['method']}") +print(f" Iterations: {EP['iter']}") +print(f" Smoothing: {EP['smooth']}") + + +# ### Step 5: Compute the Directional Spectrum +# +# Now we can run the main analysis function: + +# In[ ]: + + +# Compute directional spectrum +print("Computing directional spectrum...") +print("=" * 50) + +# Set plot type to 0 (no automatic plotting) so we can customize +options = ['PLOTTYPE', 0] + +SMout, EPout = dirspec(ID, SM, EP, options) + +print("=" * 50) +print("\nDirectional spectrum computed successfully!") +print(f"Output spectrum shape: {SMout['S'].shape}") + + +# ### Step 6: Analyze Results +# +# Use `infospec` to get key wave statistics: + +# In[ ]: + + +# Get wave information +Hsig, Tp, DTp, Dp = infospec(SMout) + +print("\n" + "="*50) +print("WAVE ANALYSIS RESULTS") +print("="*50) +print(f"Significant Wave Height (Hsig): {Hsig:.2f} m") +print(f"Peak Period (Tp): {Tp:.2f} s") +print(f"Direction at Peak (DTp): {DTp:.2f} rad = {np.degrees(DTp):.1f}°") +print(f"Dominant Direction (Dp): {Dp:.2f} rad = {np.degrees(Dp):.1f}°") +print("="*50) + + +# ### Step 7: Visualize the Directional Spectrum +# +# Create custom visualizations of the results: + +# In[ ]: + + +# Create a 1D frequency spectrum (integrated over directions) +freq_spectrum = np.sum(np.real(SMout['S']), axis=1) * (SMout['dirs'][1] - SMout['dirs'][0]) + +# Create figure with subplots +fig, axes = plt.subplots(1, 2, figsize=(14, 5)) + +# Plot 1D frequency spectrum +axes[0].plot(SMout['freqs'], freq_spectrum, 'b-', linewidth=2) +axes[0].axvline(1/Tp, color='r', linestyle='--', label=f'Peak Period = {Tp:.1f}s') +axes[0].set_xlabel('Frequency (Hz)', fontsize=12) +axes[0].set_ylabel('Spectral Density (m²/Hz)', fontsize=12) +axes[0].set_title('Frequency Spectrum (Directionally Integrated)', fontsize=13) +axes[0].grid(True, alpha=0.3) +axes[0].legend() + +# Plot 1D directional distribution (integrated over frequencies) +dir_spectrum = np.sum(np.real(SMout['S']), axis=0) * (SMout['freqs'][1] - SMout['freqs'][0]) +axes[1].plot(np.degrees(SMout['dirs']), dir_spectrum, 'g-', linewidth=2) +axes[1].axvline(np.degrees(Dp), color='r', linestyle='--', label=f'Dominant Dir = {np.degrees(Dp):.1f}°') +axes[1].set_xlabel('Direction (degrees)', fontsize=12) +axes[1].set_ylabel('Spectral Density (m²/degree)', fontsize=12) +axes[1].set_title('Directional Distribution (Frequency Integrated)', fontsize=13) +axes[1].grid(True, alpha=0.3) +axes[1].legend() + +plt.tight_layout() +plt.show() + + +# In[ ]: + + +# Create 2D contour plot of directional spectrum +fig, ax = plt.subplots(figsize=(10, 8)) + +# Convert directions to degrees for better readability +dirs_deg = np.degrees(SMout['dirs']) + +# Create meshgrid +F, D = np.meshgrid(SMout['freqs'], dirs_deg) + +# Plot filled contours +levels = 20 +contourf = ax.contourf(F, D, np.real(SMout['S']).T, levels=levels, cmap='viridis') +contour = ax.contour(F, D, np.real(SMout['S']).T, levels=levels, colors='k', alpha=0.3, linewidths=0.5) + +# Add colorbar +cbar = plt.colorbar(contourf, ax=ax, label='Spectral Density (m²s/deg)') + +# Mark peak +ax.plot(1/Tp, np.degrees(DTp), 'r*', markersize=15, label=f'Peak: T={Tp:.1f}s, θ={np.degrees(DTp):.1f}°') + +ax.set_xlabel('Frequency (Hz)', fontsize=12) +ax.set_ylabel('Direction (degrees)', fontsize=12) +ax.set_title('2D Directional Wave Spectrum', fontsize=14, fontweight='bold') +ax.legend(loc='upper right') +ax.grid(True, alpha=0.3) + +plt.tight_layout() +plt.show() + + +# ### Step 8: Use Built-in Plotting Functions +# +# pyDIWASP includes built-in plotting functions for standard visualizations: + +# In[ ]: + + +# 3D surface plot +plotspec(SMout, ptype=1) +plt.title('3D Surface Plot - Cartesian Directions', fontsize=14, fontweight='bold') +plt.show() + + +# In[ ]: + + +# Polar contour plot +plotspec(SMout, ptype=2) +plt.title('Polar Contour Plot - Cartesian Directions', fontsize=14, fontweight='bold') +plt.show() + + +# ## Example 2: Comparing Different Estimation Methods +# +# pyDIWASP supports several estimation methods. Let's compare two common ones: +# - **IMLM**: Iterated Maximum Likelihood Method (default) +# - **EMEP**: Extended Maximum Entropy Principle + +# In[ ]: + + +# Compute spectrum using EMEP method +EP_emep = { + 'method': 'EMEP', + 'iter': 100, + 'smooth': 'ON' +} + +print("Computing directional spectrum using EMEP method...") +print("=" * 50) +SMout_emep, _ = dirspec(ID, SM, EP_emep, ['PLOTTYPE', 0]) +print("=" * 50) +print("Complete!\n") + +# Get statistics for EMEP +Hsig_emep, Tp_emep, DTp_emep, Dp_emep = infospec(SMout_emep) + + +# In[ ]: + + +# Compare the two methods +fig, axes = plt.subplots(1, 2, figsize=(14, 6)) + +# IMLM method +F, D = np.meshgrid(SMout['freqs'], np.degrees(SMout['dirs'])) +c1 = axes[0].contourf(F, D, np.real(SMout['S']).T, levels=15, cmap='viridis') +axes[0].plot(1/Tp, np.degrees(DTp), 'r*', markersize=12, label=f'Peak') +axes[0].set_xlabel('Frequency (Hz)') +axes[0].set_ylabel('Direction (degrees)') +axes[0].set_title(f'IMLM Method\nHsig={Hsig:.2f}m, Tp={Tp:.1f}s', fontweight='bold') +plt.colorbar(c1, ax=axes[0], label='Density') +axes[0].legend() +axes[0].grid(True, alpha=0.3) + +# EMEP method +c2 = axes[1].contourf(F, D, np.real(SMout_emep['S']).T, levels=15, cmap='viridis') +axes[1].plot(1/Tp_emep, np.degrees(DTp_emep), 'r*', markersize=12, label=f'Peak') +axes[1].set_xlabel('Frequency (Hz)') +axes[1].set_ylabel('Direction (degrees)') +axes[1].set_title(f'EMEP Method\nHsig={Hsig_emep:.2f}m, Tp={Tp_emep:.1f}s', fontweight='bold') +plt.colorbar(c2, ax=axes[1], label='Density') +axes[1].legend() +axes[1].grid(True, alpha=0.3) + +plt.tight_layout() +plt.show() + +print("\nComparison of Results:") +print("=" * 50) +print(f"{'Parameter':<20} {'IMLM':<15} {'EMEP':<15}") +print("=" * 50) +print(f"{'Hsig (m)':<20} {Hsig:<15.2f} {Hsig_emep:<15.2f}") +print(f"{'Tp (s)':<20} {Tp:<15.2f} {Tp_emep:<15.2f}") +print(f"{'DTp (degrees)':<20} {np.degrees(DTp):<15.1f} {np.degrees(DTp_emep):<15.1f}") +print(f"{'Dp (degrees)':<20} {np.degrees(Dp):<15.1f} {np.degrees(Dp_emep):<15.1f}") +print("=" * 50) + + +# ## Summary +# +# This notebook demonstrated: +# +# 1. **Setting up input data structures** (ID, SM, EP) +# 2. **Running directional spectrum analysis** using `dirspec()` +# 3. **Extracting wave statistics** using `infospec()` +# 4. **Visualizing results** with both custom plots and built-in functions +# 5. **Comparing different estimation methods** (IMLM vs EMEP) +# +# ### Key Takeaways +# +# - pyDIWASP requires three main input structures: ID (instrument data), SM (spectral matrix), and EP (estimation parameters) +# - The IMLM method is the default and works well for most applications +# - Different estimation methods may give slightly different results +# - The output includes both the full 2D spectrum and integrated statistics +# +# ### Next Steps +# +# To use pyDIWASP with your own data: +# 1. Load your wave measurement data +# 2. Set up the ID structure with your instrument configuration +# 3. Define appropriate frequency and direction ranges in SM +# 4. Choose an estimation method in EP +# 5. Run `dirspec()` and analyze the results +# +# For more information, see the [pyDIWASP README](../README.md). diff --git a/private/__pycache__/EMEP.cpython-312.pyc b/private/__pycache__/EMEP.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df1923917582e612fa84fa9398bd90c02c52842b GIT binary patch literal 11441 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 XRSXjUz|6?Vc$-1!GYbnNKUg^cHu_>c literal 0 HcmV?d00001 diff --git a/private/__pycache__/hsig.cpython-312.pyc b/private/__pycache__/hsig.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a540f0a0c96501fcabfa8c9f81cb8147ea9fd0f GIT binary patch literal 1031 zcma)5OKTHR6u$FF+9X7!wulepl2wAGsi{!tLuga_SXA2v8|X$Z^T>=QGt+x#T2m5; z;-Ue;3PKYQR1{R)^nbVt-IQ@taMfKQyRLff)HGEPJTT{;$M>Ck=A4-y@pu%0j4wYo zuZRf!;!a1v52W1($1YNlDiqP0po%ZinphT6(h*NTP=o+=rg$|8o^}|byQqSyLPb!8 z>kv{!uFpM*ds~37iV&0kK%9A9gan8XM^t4s?4IBEq|+f}I&vW^cVgZ1zq8>kr(;#* z@4s*U`>cptA$T*1>rRf_)lemrL#ZB;0Adt_%q--ma!XkEEX6f#3%fQ}NKx^M#MLk} z3zn&y3b9zqr`eF>^G86M8y ztWB5;_dZlKV%Y^s9HWfqsO>p}IGbM?#EC10i*DW<}Hh|q#*`C@L-7gZftK%+gpfH=<>UnQSC#wR0b z!!BuQ>RFaX)0;MZns&-(JGo#`a|70qHel34PT7~aL9ap;{$aBhplay&28v#;O&>&J z+jI4sJInRkjYK2gyz_xHhgy*&WMc7cy*{=>>XVI3LvM~jX0jDa)@BZRquV3(p4!x5 zB>HOQb-tc|yRy6bX7%0l``PB~=ggPUkE37jej3}qHMBpF-b=Iw?(Mm)f${zLL@P2; zn?8)iYYT1Y0;MEBWO*eRd<49p#czoAbDRINC%qFTTlI?CeR>UCK2{b7bu0;j@Dug5 K<$mE(06zsk$MoU= literal 0 HcmV?d00001 diff --git a/private/__pycache__/pres.cpython-312.pyc b/private/__pycache__/pres.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..35d374b1fc03e4f0a54c006143fab5d75041ad34 GIT binary patch literal 687 zcmX@j%ge<81lEC#nOuwv439w^7+``jJ_`XE(-~42QW$d>q8L+{S{S03Dj7AIUxF0) zC9^`+fhaa0W(MNVKY+w^h7zEe5Re5k4JH7jYnW@8s(`vzLsbz_&4Q}BikpF0Eq^M zJDlA8;+^6X1SbSf6u--2G0FclC literal 0 HcmV?d00001 diff --git a/private/__pycache__/smoothspec.cpython-312.pyc b/private/__pycache__/smoothspec.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..679b3bcbedf88eb4af2cdca74b44335d362b5eeb GIT binary patch literal 1869 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 Date: Tue, 3 Feb 2026 01:42:25 +0000 Subject: [PATCH 4/7] Add .gitignore and remove cache files Co-authored-by: SBFRF <8375832+SBFRF@users.noreply.github.com> --- .gitignore | 65 +++ __pycache__/dirspec.cpython-312.pyc | Bin 8718 -> 0 bytes __pycache__/infospec.cpython-312.pyc | Bin 2792 -> 0 bytes __pycache__/interpspec.cpython-312.pyc | Bin 2740 -> 0 bytes __pycache__/plotspec.cpython-312.pyc | Bin 4784 -> 0 bytes __pycache__/writespec.cpython-312.pyc | Bin 1645 -> 0 bytes examples/test_notebook.py | 461 ------------------ private/__pycache__/EMEP.cpython-312.pyc | Bin 11441 -> 0 bytes private/__pycache__/IMLM.cpython-312.pyc | Bin 3567 -> 0 bytes .../__pycache__/check_data.cpython-312.pyc | Bin 5476 -> 0 bytes .../__pycache__/diwasp_csd.cpython-312.pyc | Bin 2438 -> 0 bytes private/__pycache__/elev.cpython-312.pyc | Bin 483 -> 0 bytes private/__pycache__/hsig.cpython-312.pyc | Bin 1031 -> 0 bytes private/__pycache__/pres.cpython-312.pyc | Bin 687 -> 0 bytes .../__pycache__/smoothspec.cpython-312.pyc | Bin 1869 -> 0 bytes .../__pycache__/spectobasis.cpython-312.pyc | Bin 1582 -> 0 bytes private/__pycache__/velx.cpython-312.pyc | Bin 648 -> 0 bytes private/__pycache__/vely.cpython-312.pyc | Bin 646 -> 0 bytes .../__pycache__/wavenumber.cpython-312.pyc | Bin 1508 -> 0 bytes 19 files changed, 65 insertions(+), 461 deletions(-) create mode 100644 .gitignore delete mode 100644 __pycache__/dirspec.cpython-312.pyc delete mode 100644 __pycache__/infospec.cpython-312.pyc delete mode 100644 __pycache__/interpspec.cpython-312.pyc delete mode 100644 __pycache__/plotspec.cpython-312.pyc delete mode 100644 __pycache__/writespec.cpython-312.pyc delete mode 100644 examples/test_notebook.py delete mode 100644 private/__pycache__/EMEP.cpython-312.pyc delete mode 100644 private/__pycache__/IMLM.cpython-312.pyc delete mode 100644 private/__pycache__/check_data.cpython-312.pyc delete mode 100644 private/__pycache__/diwasp_csd.cpython-312.pyc delete mode 100644 private/__pycache__/elev.cpython-312.pyc delete mode 100644 private/__pycache__/hsig.cpython-312.pyc delete mode 100644 private/__pycache__/pres.cpython-312.pyc delete mode 100644 private/__pycache__/smoothspec.cpython-312.pyc delete mode 100644 private/__pycache__/spectobasis.cpython-312.pyc delete mode 100644 private/__pycache__/velx.cpython-312.pyc delete mode 100644 private/__pycache__/vely.cpython-312.pyc delete mode 100644 private/__pycache__/wavenumber.cpython-312.pyc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..034bbbe --- /dev/null +++ b/.gitignore @@ -0,0 +1,65 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Test files +examples/test_notebook.py diff --git a/__pycache__/dirspec.cpython-312.pyc b/__pycache__/dirspec.cpython-312.pyc deleted file mode 100644 index af3b3ab4f53edb7454978c4923d539117e41ef91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8718 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 diff --git a/examples/test_notebook.py b/examples/test_notebook.py deleted file mode 100644 index a19fa10..0000000 --- a/examples/test_notebook.py +++ /dev/null @@ -1,461 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -# # pyDIWASP Example: Directional Wave Spectrum Analysis -# -# This notebook demonstrates how to use pyDIWASP to analyze directional wave spectra from instrument array data. -# -# ## Overview -# -# pyDIWASP is a Python implementation of the DIWASP (DIrectional WAve SPectrum) toolbox. It estimates the directional distribution of wave energy from measurements by an array of wave instruments. -# -# ### What is a Directional Wave Spectrum? -# -# A directional wave spectrum describes how wave energy is distributed across: -# - **Frequencies** (or wave periods) -# - **Directions** (where waves are coming from) -# -# This is more informative than a 1D frequency spectrum because it tells us not just how much energy is at each frequency, but also which direction that energy is traveling. - -# ## Setup -# -# First, import the necessary libraries: - -# In[ ]: - - -import sys -import numpy as np -import matplotlib.pyplot as plt - -# Add parent directory to path to import pyDIWASP modules -sys.path.insert(0, '..') - -from dirspec import dirspec -from infospec import infospec -from plotspec import plotspec - -# Set random seed for reproducibility -np.random.seed(42) - -print("Libraries imported successfully!") - - -# ## Example 1: Basic Directional Spectrum Analysis -# -# In this example, we'll create synthetic wave data representing a wave field with: -# - A dominant wave direction from the east (90 degrees) -# - Peak period around 10 seconds (0.1 Hz) -# - Wave height around 2 meters -# -# We'll use a simple 3-sensor array to measure pressure at different locations. - -# ### Step 1: Generate Synthetic Wave Data -# -# We'll create a simple wave field using superposition of sinusoidal components: - -# In[ ]: - - -# Simulation parameters -duration = 1024 # seconds -fs = 2.0 # sampling frequency (Hz) -depth = 10.0 # water depth (m) - -# Time array -t = np.arange(0, duration, 1/fs) -nsamples = len(t) - -# Wave parameters -peak_freq = 0.1 # Hz (10 second period) -peak_dir = np.pi/2 # radians (from East) -wave_amplitude = 1.0 # meters - -# Instrument array layout (3 pressure sensors in an L-shape) -# Layout is 3xN array: [x positions; y positions; z positions] -layout = np.array([ - [0, 10, 0], # x positions (meters) - [0, 0, 10], # y positions (meters) - [-depth, -depth, -depth] # z positions (meters, negative = below surface) -]) - -ninst = layout.shape[1] - -print(f"Simulation setup:") -print(f" Duration: {duration} seconds") -print(f" Sampling rate: {fs} Hz") -print(f" Number of samples: {nsamples}") -print(f" Number of instruments: {ninst}") -print(f" Water depth: {depth} m") -print(f"\nInstrument positions (x, y, z):") -for i in range(ninst): - print(f" Sensor {i+1}: ({layout[0,i]:.1f}, {layout[1,i]:.1f}, {layout[2,i]:.1f}) m") - - -# In[ ]: - - -# Generate synthetic wave data -# Simple model: superposition of wave components - -# Create several wave components with different frequencies and directions -frequencies = np.array([0.08, 0.10, 0.12, 0.15]) # Hz -directions = np.array([np.pi/2, np.pi/2, np.pi/2 + np.pi/6, np.pi/3]) # radians -amplitudes = np.array([0.5, 1.0, 0.3, 0.2]) # meters - -# Calculate wavenumbers using dispersion relation: omega^2 = g*k*tanh(k*h) -g = 9.81 # gravity (m/s^2) -wavenumbers = [] -for f in frequencies: - omega = 2 * np.pi * f - # Solve iteratively for k - k = omega**2 / g # deep water approximation as initial guess - for _ in range(10): - k = omega**2 / (g * np.tanh(k * depth)) - wavenumbers.append(k) -wavenumbers = np.array(wavenumbers) - -# Initialize data array -data = np.zeros((nsamples, ninst)) - -# Generate pressure signal at each instrument location -for i in range(len(frequencies)): - f = frequencies[i] - theta = directions[i] - A = amplitudes[i] - k = wavenumbers[i] - omega = 2 * np.pi * f - - # Phase at each instrument location - for j in range(ninst): - x = layout[0, j] - y = layout[1, j] - z = layout[2, j] - - # Spatial phase - phase_xy = k * (x * np.cos(theta) + y * np.sin(theta)) - - # Pressure response (includes depth attenuation) - pressure_response = np.cosh(k * (z + depth)) / np.cosh(k * depth) - - # Add wave component - random_phase = np.random.uniform(0, 2*np.pi) - data[:, j] += A * pressure_response * np.cos(omega * t - phase_xy + random_phase) - -# Add some noise -noise_level = 0.05 # 5% noise -data += noise_level * np.random.randn(nsamples, ninst) - -print(f"\nGenerated wave data shape: {data.shape}") -print(f"Data range: [{data.min():.2f}, {data.max():.2f}] (pressure units)") - - -# ### Visualize the Generated Data - -# In[ ]: - - -# Plot time series from each instrument -fig, axes = plt.subplots(ninst, 1, figsize=(12, 6), sharex=True) -for i in range(ninst): - axes[i].plot(t[:500], data[:500, i]) - axes[i].set_ylabel(f'Sensor {i+1}\n(pressure)') - axes[i].grid(True, alpha=0.3) - -axes[-1].set_xlabel('Time (seconds)') -fig.suptitle('Synthetic Wave Measurements (first 250 seconds)', fontsize=14) -plt.tight_layout() -plt.show() - - -# ### Step 2: Set Up the Instrument Data Structure -# -# The Instrument Data (ID) structure contains all information about the measurement array: - -# In[ ]: - - -# Create instrument data structure -ID = { - 'layout': layout, - 'datatypes': ['pres', 'pres', 'pres'], # all pressure sensors - 'depth': depth, - 'fs': fs, - 'data': data -} - -print("Instrument Data (ID) structure created:") -print(f" Layout shape: {ID['layout'].shape}") -print(f" Data types: {ID['datatypes']}") -print(f" Depth: {ID['depth']} m") -print(f" Sampling frequency: {ID['fs']} Hz") -print(f" Data shape: {ID['data'].shape}") - - -# ### Step 3: Define the Spectral Matrix Structure -# -# The Spectral Matrix (SM) structure defines the frequency and directional resolution of the output: - -# In[ ]: - - -# Define output spectral matrix -SM = { - 'freqs': np.linspace(0.05, 0.3, 30), # frequency bins (Hz) - 'dirs': np.linspace(-np.pi, np.pi, 36) # direction bins (radians) -} - -print("Spectral Matrix (SM) structure created:") -print(f" Frequency range: {SM['freqs'][0]:.3f} - {SM['freqs'][-1]:.3f} Hz") -print(f" Number of frequency bins: {len(SM['freqs'])}") -print(f" Direction range: {SM['dirs'][0]:.2f} - {SM['dirs'][-1]:.2f} radians") -print(f" Number of direction bins: {len(SM['dirs'])}") -print(f" Directional resolution: {np.degrees(SM['dirs'][1] - SM['dirs'][0]):.1f} degrees") - - -# ### Step 4: Set Estimation Parameters -# -# The Estimation Parameters (EP) structure controls the analysis method: - -# In[ ]: - - -# Define estimation parameters -EP = { - 'method': 'IMLM', # Iterated Maximum Likelihood Method - 'iter': 100, # number of iterations - 'smooth': 'ON' # enable spectral smoothing -} - -print("Estimation Parameters (EP) structure created:") -print(f" Method: {EP['method']}") -print(f" Iterations: {EP['iter']}") -print(f" Smoothing: {EP['smooth']}") - - -# ### Step 5: Compute the Directional Spectrum -# -# Now we can run the main analysis function: - -# In[ ]: - - -# Compute directional spectrum -print("Computing directional spectrum...") -print("=" * 50) - -# Set plot type to 0 (no automatic plotting) so we can customize -options = ['PLOTTYPE', 0] - -SMout, EPout = dirspec(ID, SM, EP, options) - -print("=" * 50) -print("\nDirectional spectrum computed successfully!") -print(f"Output spectrum shape: {SMout['S'].shape}") - - -# ### Step 6: Analyze Results -# -# Use `infospec` to get key wave statistics: - -# In[ ]: - - -# Get wave information -Hsig, Tp, DTp, Dp = infospec(SMout) - -print("\n" + "="*50) -print("WAVE ANALYSIS RESULTS") -print("="*50) -print(f"Significant Wave Height (Hsig): {Hsig:.2f} m") -print(f"Peak Period (Tp): {Tp:.2f} s") -print(f"Direction at Peak (DTp): {DTp:.2f} rad = {np.degrees(DTp):.1f}°") -print(f"Dominant Direction (Dp): {Dp:.2f} rad = {np.degrees(Dp):.1f}°") -print("="*50) - - -# ### Step 7: Visualize the Directional Spectrum -# -# Create custom visualizations of the results: - -# In[ ]: - - -# Create a 1D frequency spectrum (integrated over directions) -freq_spectrum = np.sum(np.real(SMout['S']), axis=1) * (SMout['dirs'][1] - SMout['dirs'][0]) - -# Create figure with subplots -fig, axes = plt.subplots(1, 2, figsize=(14, 5)) - -# Plot 1D frequency spectrum -axes[0].plot(SMout['freqs'], freq_spectrum, 'b-', linewidth=2) -axes[0].axvline(1/Tp, color='r', linestyle='--', label=f'Peak Period = {Tp:.1f}s') -axes[0].set_xlabel('Frequency (Hz)', fontsize=12) -axes[0].set_ylabel('Spectral Density (m²/Hz)', fontsize=12) -axes[0].set_title('Frequency Spectrum (Directionally Integrated)', fontsize=13) -axes[0].grid(True, alpha=0.3) -axes[0].legend() - -# Plot 1D directional distribution (integrated over frequencies) -dir_spectrum = np.sum(np.real(SMout['S']), axis=0) * (SMout['freqs'][1] - SMout['freqs'][0]) -axes[1].plot(np.degrees(SMout['dirs']), dir_spectrum, 'g-', linewidth=2) -axes[1].axvline(np.degrees(Dp), color='r', linestyle='--', label=f'Dominant Dir = {np.degrees(Dp):.1f}°') -axes[1].set_xlabel('Direction (degrees)', fontsize=12) -axes[1].set_ylabel('Spectral Density (m²/degree)', fontsize=12) -axes[1].set_title('Directional Distribution (Frequency Integrated)', fontsize=13) -axes[1].grid(True, alpha=0.3) -axes[1].legend() - -plt.tight_layout() -plt.show() - - -# In[ ]: - - -# Create 2D contour plot of directional spectrum -fig, ax = plt.subplots(figsize=(10, 8)) - -# Convert directions to degrees for better readability -dirs_deg = np.degrees(SMout['dirs']) - -# Create meshgrid -F, D = np.meshgrid(SMout['freqs'], dirs_deg) - -# Plot filled contours -levels = 20 -contourf = ax.contourf(F, D, np.real(SMout['S']).T, levels=levels, cmap='viridis') -contour = ax.contour(F, D, np.real(SMout['S']).T, levels=levels, colors='k', alpha=0.3, linewidths=0.5) - -# Add colorbar -cbar = plt.colorbar(contourf, ax=ax, label='Spectral Density (m²s/deg)') - -# Mark peak -ax.plot(1/Tp, np.degrees(DTp), 'r*', markersize=15, label=f'Peak: T={Tp:.1f}s, θ={np.degrees(DTp):.1f}°') - -ax.set_xlabel('Frequency (Hz)', fontsize=12) -ax.set_ylabel('Direction (degrees)', fontsize=12) -ax.set_title('2D Directional Wave Spectrum', fontsize=14, fontweight='bold') -ax.legend(loc='upper right') -ax.grid(True, alpha=0.3) - -plt.tight_layout() -plt.show() - - -# ### Step 8: Use Built-in Plotting Functions -# -# pyDIWASP includes built-in plotting functions for standard visualizations: - -# In[ ]: - - -# 3D surface plot -plotspec(SMout, ptype=1) -plt.title('3D Surface Plot - Cartesian Directions', fontsize=14, fontweight='bold') -plt.show() - - -# In[ ]: - - -# Polar contour plot -plotspec(SMout, ptype=2) -plt.title('Polar Contour Plot - Cartesian Directions', fontsize=14, fontweight='bold') -plt.show() - - -# ## Example 2: Comparing Different Estimation Methods -# -# pyDIWASP supports several estimation methods. Let's compare two common ones: -# - **IMLM**: Iterated Maximum Likelihood Method (default) -# - **EMEP**: Extended Maximum Entropy Principle - -# In[ ]: - - -# Compute spectrum using EMEP method -EP_emep = { - 'method': 'EMEP', - 'iter': 100, - 'smooth': 'ON' -} - -print("Computing directional spectrum using EMEP method...") -print("=" * 50) -SMout_emep, _ = dirspec(ID, SM, EP_emep, ['PLOTTYPE', 0]) -print("=" * 50) -print("Complete!\n") - -# Get statistics for EMEP -Hsig_emep, Tp_emep, DTp_emep, Dp_emep = infospec(SMout_emep) - - -# In[ ]: - - -# Compare the two methods -fig, axes = plt.subplots(1, 2, figsize=(14, 6)) - -# IMLM method -F, D = np.meshgrid(SMout['freqs'], np.degrees(SMout['dirs'])) -c1 = axes[0].contourf(F, D, np.real(SMout['S']).T, levels=15, cmap='viridis') -axes[0].plot(1/Tp, np.degrees(DTp), 'r*', markersize=12, label=f'Peak') -axes[0].set_xlabel('Frequency (Hz)') -axes[0].set_ylabel('Direction (degrees)') -axes[0].set_title(f'IMLM Method\nHsig={Hsig:.2f}m, Tp={Tp:.1f}s', fontweight='bold') -plt.colorbar(c1, ax=axes[0], label='Density') -axes[0].legend() -axes[0].grid(True, alpha=0.3) - -# EMEP method -c2 = axes[1].contourf(F, D, np.real(SMout_emep['S']).T, levels=15, cmap='viridis') -axes[1].plot(1/Tp_emep, np.degrees(DTp_emep), 'r*', markersize=12, label=f'Peak') -axes[1].set_xlabel('Frequency (Hz)') -axes[1].set_ylabel('Direction (degrees)') -axes[1].set_title(f'EMEP Method\nHsig={Hsig_emep:.2f}m, Tp={Tp_emep:.1f}s', fontweight='bold') -plt.colorbar(c2, ax=axes[1], label='Density') -axes[1].legend() -axes[1].grid(True, alpha=0.3) - -plt.tight_layout() -plt.show() - -print("\nComparison of Results:") -print("=" * 50) -print(f"{'Parameter':<20} {'IMLM':<15} {'EMEP':<15}") -print("=" * 50) -print(f"{'Hsig (m)':<20} {Hsig:<15.2f} {Hsig_emep:<15.2f}") -print(f"{'Tp (s)':<20} {Tp:<15.2f} {Tp_emep:<15.2f}") -print(f"{'DTp (degrees)':<20} {np.degrees(DTp):<15.1f} {np.degrees(DTp_emep):<15.1f}") -print(f"{'Dp (degrees)':<20} {np.degrees(Dp):<15.1f} {np.degrees(Dp_emep):<15.1f}") -print("=" * 50) - - -# ## Summary -# -# This notebook demonstrated: -# -# 1. **Setting up input data structures** (ID, SM, EP) -# 2. **Running directional spectrum analysis** using `dirspec()` -# 3. **Extracting wave statistics** using `infospec()` -# 4. **Visualizing results** with both custom plots and built-in functions -# 5. **Comparing different estimation methods** (IMLM vs EMEP) -# -# ### Key Takeaways -# -# - pyDIWASP requires three main input structures: ID (instrument data), SM (spectral matrix), and EP (estimation parameters) -# - The IMLM method is the default and works well for most applications -# - Different estimation methods may give slightly different results -# - The output includes both the full 2D spectrum and integrated statistics -# -# ### Next Steps -# -# To use pyDIWASP with your own data: -# 1. Load your wave measurement data -# 2. Set up the ID structure with your instrument configuration -# 3. Define appropriate frequency and direction ranges in SM -# 4. Choose an estimation method in EP -# 5. Run `dirspec()` and analyze the results -# -# For more information, see the [pyDIWASP README](../README.md). diff --git a/private/__pycache__/EMEP.cpython-312.pyc b/private/__pycache__/EMEP.cpython-312.pyc deleted file mode 100644 index df1923917582e612fa84fa9398bd90c02c52842b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11441 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 XRSXjUz|6?Vc$-1!GYbnNKUg^cHu_>c diff --git a/private/__pycache__/hsig.cpython-312.pyc b/private/__pycache__/hsig.cpython-312.pyc deleted file mode 100644 index 1a540f0a0c96501fcabfa8c9f81cb8147ea9fd0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcma)5OKTHR6u$FF+9X7!wulepl2wAGsi{!tLuga_SXA2v8|X$Z^T>=QGt+x#T2m5; z;-Ue;3PKYQR1{R)^nbVt-IQ@taMfKQyRLff)HGEPJTT{;$M>Ck=A4-y@pu%0j4wYo zuZRf!;!a1v52W1($1YNlDiqP0po%ZinphT6(h*NTP=o+=rg$|8o^}|byQqSyLPb!8 z>kv{!uFpM*ds~37iV&0kK%9A9gan8XM^t4s?4IBEq|+f}I&vW^cVgZ1zq8>kr(;#* z@4s*U`>cptA$T*1>rRf_)lemrL#ZB;0Adt_%q--ma!XkEEX6f#3%fQ}NKx^M#MLk} z3zn&y3b9zqr`eF>^G86M8y ztWB5;_dZlKV%Y^s9HWfqsO>p}IGbM?#EC10i*DW<}Hh|q#*`C@L-7gZftK%+gpfH=<>UnQSC#wR0b z!!BuQ>RFaX)0;MZns&-(JGo#`a|70qHel34PT7~aL9ap;{$aBhplay&28v#;O&>&J z+jI4sJInRkjYK2gyz_xHhgy*&WMc7cy*{=>>XVI3LvM~jX0jDa)@BZRquV3(p4!x5 zB>HOQb-tc|yRy6bX7%0l``PB~=ggPUkE37jej3}qHMBpF-b=Iw?(Mm)f${zLL@P2; zn?8)iYYT1Y0;MEBWO*eRd<49p#czoAbDRINC%qFTTlI?CeR>UCK2{b7bu0;j@Dug5 K<$mE(06zsk$MoU= diff --git a/private/__pycache__/pres.cpython-312.pyc b/private/__pycache__/pres.cpython-312.pyc deleted file mode 100644 index 35d374b1fc03e4f0a54c006143fab5d75041ad34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 687 zcmX@j%ge<81lEC#nOuwv439w^7+``jJ_`XE(-~42QW$d>q8L+{S{S03Dj7AIUxF0) zC9^`+fhaa0W(MNVKY+w^h7zEe5Re5k4JH7jYnW@8s(`vzLsbz_&4Q}BikpF0Eq^M zJDlA8;+^6X1SbSf6u--2G0FclC diff --git a/private/__pycache__/smoothspec.cpython-312.pyc b/private/__pycache__/smoothspec.cpython-312.pyc deleted file mode 100644 index 679b3bcbedf88eb4af2cdca74b44335d362b5eeb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1869 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 Date: Mon, 2 Feb 2026 21:00:38 -0500 Subject: [PATCH 5/7] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aab21aa..e7d55c3 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Converted from: https://github.com/metocean/diwasp ## Features - **Directional Wave Analysis**: Estimate directional wave spectra from instrument array data -- **Multiple Estimation Methods**: Supports IMLM, EMEP, EMLM, DFTM, and BDM methods +- **Multiple Estimation Methods**: Supports IMLM and EMEP methods - **Flexible Input**: Works with various instrument types (pressure, velocity, elevation sensors) - **Visualization**: Built-in plotting functions for 3D and polar spectral plots - **Wave Statistics**: Calculate significant wave height, peak period, and dominant direction From 80c9a2e695c8a82bebd65c19ce5b8e2230f824ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 02:03:22 +0000 Subject: [PATCH 6/7] Remove unimplemented methods from docs and improve notebook imports Co-authored-by: SBFRF <8375832+SBFRF@users.noreply.github.com> --- README.md | 9 ++------- examples/pyDIWASP_example.ipynb | 32 +++++++++++++++++++++----------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index e7d55c3..47ac01e 100644 --- a/README.md +++ b/README.md @@ -122,8 +122,6 @@ Calculate instrument response to waves: Directional spectrum estimation algorithms: - `IMLM.py` - Iterated Maximum Likelihood Method (default) - `EMEP.py` - Extended Maximum Entropy Principle -- `EMLM.py` - Extended Maximum Likelihood Method -- (Additional methods: DFTM, BDM) ### Utility Functions - `smoothspec.py` - Spectral smoothing @@ -153,7 +151,7 @@ Dictionary with the following fields: ### Estimation Parameters Structure (EP) Dictionary with the following fields: -- `method`: Estimation method ('IMLM', 'EMEP', 'EMLM', 'DFTM', 'BDM') +- `method`: Estimation method ('IMLM' or 'EMEP') - `nfft`: FFT length (auto-calculated if not specified) - `dres`: Directional resolution (default: 180) - `iter`: Number of iterations for iterative methods (default: 100) @@ -172,10 +170,7 @@ Dictionary with the following fields: - Works well for narrow directional spreads - Suitable for swell-dominated conditions -### Other Methods -- **EMLM**: Extended Maximum Likelihood Method -- **DFTM**: Direct Fourier Transform Method -- **BDM**: Bayesian Directional Method +**Note:** The original DIWASP MATLAB toolbox includes additional methods (EMLM, DFTM, BDM) that are not yet implemented in this Python version. Contributions to add these methods are welcome! ## Examples diff --git a/examples/pyDIWASP_example.ipynb b/examples/pyDIWASP_example.ipynb index a947706..f084b04 100644 --- a/examples/pyDIWASP_example.ipynb +++ b/examples/pyDIWASP_example.ipynb @@ -27,7 +27,11 @@ "source": [ "## Setup\n", "\n", - "First, import the necessary libraries:" + "First, import the necessary libraries:\n", + "\n", + "**Note on imports:** This notebook assumes you're running it from the `examples/` directory. If you encounter import errors, you can either:\n", + "1. Install pyDIWASP in development mode: `pip install -e .` (from repo root)\n", + "2. Ensure your working directory is the `examples/` folder when starting Jupyter\n" ] }, { @@ -37,11 +41,17 @@ "outputs": [], "source": [ "import sys\n", + "import os\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", - "# Add parent directory to path to import pyDIWASP modules\n", - "sys.path.insert(0, '..')\n", + "# Add pyDIWASP to path for imports\n", + "# This notebook should be run from the examples/ directory\n", + "# Alternative: install pyDIWASP with `pip install -e .` from repo root\n", + "notebook_dir = os.path.dirname(os.path.abspath('.'))\n", + "repo_root = os.path.dirname(notebook_dir) if 'examples' in notebook_dir else notebook_dir\n", + "if repo_root not in sys.path:\n", + " sys.path.insert(0, repo_root)\n", "\n", "from dirspec import dirspec\n", "from infospec import infospec\n", @@ -345,8 +355,8 @@ "print(\"=\"*50)\n", "print(f\"Significant Wave Height (Hsig): {Hsig:.2f} m\")\n", "print(f\"Peak Period (Tp): {Tp:.2f} s\")\n", - "print(f\"Direction at Peak (DTp): {DTp:.2f} rad = {np.degrees(DTp):.1f}°\")\n", - "print(f\"Dominant Direction (Dp): {Dp:.2f} rad = {np.degrees(Dp):.1f}°\")\n", + "print(f\"Direction at Peak (DTp): {DTp:.2f} rad = {np.degrees(DTp):.1f}\u00b0\")\n", + "print(f\"Dominant Direction (Dp): {Dp:.2f} rad = {np.degrees(Dp):.1f}\u00b0\")\n", "print(\"=\"*50)" ] }, @@ -375,7 +385,7 @@ "axes[0].plot(SMout['freqs'], freq_spectrum, 'b-', linewidth=2)\n", "axes[0].axvline(1/Tp, color='r', linestyle='--', label=f'Peak Period = {Tp:.1f}s')\n", "axes[0].set_xlabel('Frequency (Hz)', fontsize=12)\n", - "axes[0].set_ylabel('Spectral Density (m²/Hz)', fontsize=12)\n", + "axes[0].set_ylabel('Spectral Density (m\u00b2/Hz)', fontsize=12)\n", "axes[0].set_title('Frequency Spectrum (Directionally Integrated)', fontsize=13)\n", "axes[0].grid(True, alpha=0.3)\n", "axes[0].legend()\n", @@ -383,9 +393,9 @@ "# Plot 1D directional distribution (integrated over frequencies)\n", "dir_spectrum = np.sum(np.real(SMout['S']), axis=0) * (SMout['freqs'][1] - SMout['freqs'][0])\n", "axes[1].plot(np.degrees(SMout['dirs']), dir_spectrum, 'g-', linewidth=2)\n", - "axes[1].axvline(np.degrees(Dp), color='r', linestyle='--', label=f'Dominant Dir = {np.degrees(Dp):.1f}°')\n", + "axes[1].axvline(np.degrees(Dp), color='r', linestyle='--', label=f'Dominant Dir = {np.degrees(Dp):.1f}\u00b0')\n", "axes[1].set_xlabel('Direction (degrees)', fontsize=12)\n", - "axes[1].set_ylabel('Spectral Density (m²/degree)', fontsize=12)\n", + "axes[1].set_ylabel('Spectral Density (m\u00b2/degree)', fontsize=12)\n", "axes[1].set_title('Directional Distribution (Frequency Integrated)', fontsize=13)\n", "axes[1].grid(True, alpha=0.3)\n", "axes[1].legend()\n", @@ -415,10 +425,10 @@ "contour = ax.contour(F, D, np.real(SMout['S']).T, levels=levels, colors='k', alpha=0.3, linewidths=0.5)\n", "\n", "# Add colorbar\n", - "cbar = plt.colorbar(contourf, ax=ax, label='Spectral Density (m²s/deg)')\n", + "cbar = plt.colorbar(contourf, ax=ax, label='Spectral Density (m\u00b2s/deg)')\n", "\n", "# Mark peak\n", - "ax.plot(1/Tp, np.degrees(DTp), 'r*', markersize=15, label=f'Peak: T={Tp:.1f}s, θ={np.degrees(DTp):.1f}°')\n", + "ax.plot(1/Tp, np.degrees(DTp), 'r*', markersize=15, label=f'Peak: T={Tp:.1f}s, \u03b8={np.degrees(DTp):.1f}\u00b0')\n", "\n", "ax.set_xlabel('Frequency (Hz)', fontsize=12)\n", "ax.set_ylabel('Direction (degrees)', fontsize=12)\n", @@ -596,4 +606,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file From 772058fac9146de5cd95b70e675ed60a2ab51566 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 02:04:24 +0000 Subject: [PATCH 7/7] Simplify notebook path handling based on code review Co-authored-by: SBFRF <8375832+SBFRF@users.noreply.github.com> --- examples/pyDIWASP_example.ipynb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/pyDIWASP_example.ipynb b/examples/pyDIWASP_example.ipynb index f084b04..4b372e3 100644 --- a/examples/pyDIWASP_example.ipynb +++ b/examples/pyDIWASP_example.ipynb @@ -45,13 +45,13 @@ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", - "# Add pyDIWASP to path for imports\n", - "# This notebook should be run from the examples/ directory\n", - "# Alternative: install pyDIWASP with `pip install -e .` from repo root\n", - "notebook_dir = os.path.dirname(os.path.abspath('.'))\n", - "repo_root = os.path.dirname(notebook_dir) if 'examples' in notebook_dir else notebook_dir\n", - "if repo_root not in sys.path:\n", - " sys.path.insert(0, repo_root)\n", + "# Add pyDIWASP to Python path\n", + "# This assumes you're running the notebook from the examples/ directory\n", + "# The parent directory (..) contains the pyDIWASP modules\n", + "if os.getcwd().endswith('examples'):\n", + " sys.path.insert(0, '..')\n", + "elif os.path.basename(os.getcwd()) != 'pyDIWASP':\n", + " print('Warning: Notebook should be run from examples/ directory or repo root')\n", "\n", "from dirspec import dirspec\n", "from infospec import infospec\n",