From 1e76899f1cd806c4c38380e986ed5525cc31789a Mon Sep 17 00:00:00 2001 From: James Gallagher Date: Sun, 8 Mar 2026 10:16:25 -0600 Subject: [PATCH 1/4] Added the DAP 2 spec and a codex-generated deep-dive --- .gitignore | 1 - ...allagher et al_DAP 2.0_ESE-RFC-004v1.2.pdf | Bin 0 -> 335358 bytes docs/deep-dive-codex.md | 47 ++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 docs/2007_10_10_Gallagher et al_DAP 2.0_ESE-RFC-004v1.2.pdf create mode 100644 docs/deep-dive-codex.md diff --git a/.gitignore b/.gitignore index 0ddcb8676..7a2187081 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,6 @@ lex.*.cc ## Top-level generated artifacts Default -docs html latex tmp diff --git a/docs/2007_10_10_Gallagher et al_DAP 2.0_ESE-RFC-004v1.2.pdf b/docs/2007_10_10_Gallagher et al_DAP 2.0_ESE-RFC-004v1.2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1b161c05c0539700bdee4c060ad968441e00c7b5 GIT binary patch literal 335358 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

Gz|3|#5=A@J25 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%0D89R#H5*~coquX@q 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%0A4?f30(j)erH0c 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

RH9ibzwH*B`|!a;p^a$UZn!Xo6!k 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 literal 0 HcmV?d00001 diff --git a/docs/deep-dive-codex.md b/docs/deep-dive-codex.md new file mode 100644 index 000000000..7b76ccc96 --- /dev/null +++ b/docs/deep-dive-codex.md @@ -0,0 +1,47 @@ +**Deep Dive Summary** +- This repo is a mature C++ DAP stack with clear layering: core protocol/data model (`libdap`), HTTP/client transport (`libdapclient`), and server filter helpers (`libdapserver`). +- It implements both DAP2 and DAP4 in one codebase, with shared type infrastructure and protocol-specific parsers/serializers. +- The implementation is production-oriented and heavily tested, but there are a few explicit DAP4 “not yet implemented” paths you should treat as known gaps. + +**Library Boundaries** +- `libdap` is the core and includes DAP2 + DAP4 model, parsers, marshalling, CE logic, and common utilities: [CMakeLists.txt](/Users/jimg/src/opendap/hyrax_git/libdap4/CMakeLists.txt#L275), [Makefile.am](/Users/jimg/src/opendap/hyrax_git/libdap4/Makefile.am#L64), [libdap.pc.in](/Users/jimg/src/opendap/hyrax_git/libdap4/libdap.pc.in). +- `libdapclient` adds client-facing connect/config/HTTP layers: [CMakeLists.txt](/Users/jimg/src/opendap/hyrax_git/libdap4/CMakeLists.txt#L299), [Makefile.am](/Users/jimg/src/opendap/hyrax_git/libdap4/Makefile.am#L71), [libdapclient.pc.in](/Users/jimg/src/opendap/hyrax_git/libdap4/libdapclient.pc.in). +- `libdapserver` contains server filter primitives for CGI-style handlers: [CMakeLists.txt](/Users/jimg/src/opendap/hyrax_git/libdap4/CMakeLists.txt#L324), [Makefile.am](/Users/jimg/src/opendap/hyrax_git/libdap4/Makefile.am#L76), [libdapserver.pc.in](/Users/jimg/src/opendap/hyrax_git/libdap4/libdapserver.pc.in). + +**DAP2 Implementation Path** +- DAP2 client workflow centers on `Connect`: request DAS/DDS/DDX/DataDDS, parse MIME metadata, parse DDS text, then XDR-unmarshal data: [Connect.h](/Users/jimg/src/opendap/hyrax_git/libdap4/Connect.h#L127), [Connect.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/Connect.cc#L71), [Connect.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/Connect.cc#L250). +- DAP2 CE selection/function flow is parser-driven (`ce_expr.yy/.lex`) into `ConstraintEvaluator` clauses: [ConstraintEvaluator.h](/Users/jimg/src/opendap/hyrax_git/libdap4/ConstraintEvaluator.h#L41), [ConstraintEvaluator.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/ConstraintEvaluator.cc#L333). +- Server-side response shaping is `DODSFilter` + CE + marshalling: [DODSFilter.h](/Users/jimg/src/opendap/hyrax_git/libdap4/DODSFilter.h#L174), [DODSFilter.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/DODSFilter.cc#L89). + +**DAP4 Implementation Path** +- DAP4 client workflow is `D4Connect`: build DAP4 query keys (`dap4.ce`, `dap4.checksum`), fetch `.dmr` or `.dap`, parse DMR first chunk, then stream-unmarshal payload: [D4Connect.h](/Users/jimg/src/opendap/hyrax_git/libdap4/D4Connect.h#L54), [D4Connect.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/D4Connect.cc#L359), [D4Connect.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/D4Connect.cc#L374). +- DMR is the DAP4 root object and supports DAP2↔DAP4 transforms (`build_using_dds`, `getDDS`): [DMR.h](/Users/jimg/src/opendap/hyrax_git/libdap4/DMR.h#L48), [DMR.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/DMR.cc#L156), [DMR.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/DMR.cc#L202). +- DMR parsing uses libxml2 SAX with strict/permissive map handling mode: [D4ParserSax2.h](/Users/jimg/src/opendap/hyrax_git/libdap4/D4ParserSax2.h#L75), [D4ParserSax2.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/D4ParserSax2.cc#L310). + +**Data Model Core** +- `BaseType` is the type-invariant root used across DAP2 and DAP4; it tracks projection state, attrs, parentage, transform hooks: [BaseType.h](/Users/jimg/src/opendap/hyrax_git/libdap4/BaseType.h#L118). +- Type enum includes classic DAP2 and DAP4 additions (`Int64`, `UInt64`, `Enum`, `Opaque`, `Group`): [Type.h](/Users/jimg/src/opendap/hyrax_git/libdap4/Type.h#L94). + +**Transport and I/O** +- HTTP transport is libcurl-based `HTTPConnect`, with DAP header parsing, optional cache integration, cookies/proxy/no_proxy, and C++ stream mode for DAP4: [HTTPConnect.h](/Users/jimg/src/opendap/hyrax_git/libdap4/http_dap/HTTPConnect.h#L52), [HTTPConnect.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/http_dap/HTTPConnect.cc#L557). +- DAP4 payload transfer uses chunked framing and receiver-makes-right byte handling: [chunked_stream.h](/Users/jimg/src/opendap/hyrax_git/libdap4/chunked_stream.h#L11), [chunked_istream.h](/Users/jimg/src/opendap/hyrax_git/libdap4/chunked_istream.h#L42), [chunked_istream.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/chunked_istream.cc#L94). + +**Serialization** +- DAP2 serialization path uses XDR stream/file marshallers: [XDRStreamMarshaller.h](/Users/jimg/src/opendap/hyrax_git/libdap4/XDRStreamMarshaller.h#L53). +- DAP4 serialization path uses `D4StreamMarshaller` with optional checksums and stream-first encoding: [D4StreamMarshaller.h](/Users/jimg/src/opendap/hyrax_git/libdap4/D4StreamMarshaller.h#L58), [D4StreamMarshaller.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/D4StreamMarshaller.cc#L118). + +**Constraint/Function Engines** +- DAP4 CE parser/evaluator (`d4_ce`) supports array slicing, shared-dim behavior, map pruning, and filter clauses; includes XXS-aware error redaction: [D4ConstraintEvaluator.h](/Users/jimg/src/opendap/hyrax_git/libdap4/d4_ce/D4ConstraintEvaluator.h#L48), [D4ConstraintEvaluator.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/d4_ce/D4ConstraintEvaluator.cc#L42), [D4ConstraintEvaluator.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/d4_ce/D4ConstraintEvaluator.cc#L181). +- Server function registry is a singleton (`ServerFunctionsList`) used by DAP2 and DAP4 function paths: [ServerFunctionsList.h](/Users/jimg/src/opendap/hyrax_git/libdap4/ServerFunctionsList.h#L46), [ServerFunctionsList.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/ServerFunctionsList.cc#L65), [D4FunctionEvaluator.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/d4_function/D4FunctionEvaluator.cc#L104). + +**Tooling and Quality** +- Scale: ~207 `.cc`, ~234 `.h`, 7 parser grammars, 7 lexer files. +- Strong testing surface: autotest integration suites and ~60 CppUnit-style unit tests, including DAP4 round-trips and DMR↔DAP2 translation: [tests/Makefile.am](/Users/jimg/src/opendap/hyrax_git/libdap4/tests/Makefile.am#L27), [unit-tests/Makefile.am](/Users/jimg/src/opendap/hyrax_git/libdap4/unit-tests/Makefile.am#L83). +- Pre-commit and CI hooks are in place for formatting/lint sanity: [pre-commit.yml](/Users/jimg/src/opendap/hyrax_git/libdap4/.github/workflows/pre-commit.yml#L1), [.pre-commit-config.yaml](/Users/jimg/src/opendap/hyrax_git/libdap4/.pre-commit-config.yaml#L1). + +**Notable Gaps/Risks** +- DAP4 error-body handling is explicitly unfinished in client paths (`FIXME`/throw): [D4Connect.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/D4Connect.cc#L131), [D4Connect.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/D4Connect.cc#L404). +- Some legacy/deprecated config/proxy paths remain and increase maintenance surface: [RCReader.h](/Users/jimg/src/opendap/hyrax_git/libdap4/RCReader.h#L93), [README.dodsrc](/Users/jimg/src/opendap/hyrax_git/libdap4/README.dodsrc). +- Mixed old/new build systems (Autotools + CMake) are both supported; good for portability, but raises drift risk: [Makefile.am](/Users/jimg/src/opendap/hyrax_git/libdap4/Makefile.am#L1), [CMakeLists.txt](/Users/jimg/src/opendap/hyrax_git/libdap4/CMakeLists.txt#L1). + +If you want, I can do a second-pass deep dive focused only on one of these: `libdap` internals, `libdapclient` transport/cache behavior, or `libdapserver` handler integration patterns. \ No newline at end of file From e48a84c90500915f83e6a74385b3a47a35797d86 Mon Sep 17 00:00:00 2001 From: James Gallagher Date: Sun, 8 Mar 2026 10:26:50 -0600 Subject: [PATCH 2/4] Copied Ed Hartnett's windsurf files from NEP to windsurf-hartnett --- windsurf-harnett/rules/doc-check.md | 13 + windsurf-harnett/rules/local-build-command.md | 68 ++ windsurf-harnett/rules/slow-network.md | 48 ++ windsurf-harnett/skills/linkedin-posts.md | 246 ++++++ .../skills/netcdf-architecture/README.md | 59 ++ .../skills/netcdf-architecture/SKILL.md | 497 ++++++++++++ .../references/COMPONENTS.md | 461 ++++++++++++ .../references/DATA-STRUCTURES.md | 482 ++++++++++++ .../references/DISPATCH-TABLES.md | 707 ++++++++++++++++++ .../references/EXAMPLES.md | 463 ++++++++++++ .../references/FORTRAN-INTERFACE.md | 379 ++++++++++ .../references/UDF-PLUGINS.md | 551 ++++++++++++++ windsurf-harnett/skills/netcdf-java/skill.md | 315 ++++++++ windsurf-harnett/skills/opendap/README.md | 34 + windsurf-harnett/skills/opendap/SKILL.md | 378 ++++++++++ .../skills/opendap/references/CLIENT-USAGE.md | 339 +++++++++ .../skills/opendap/references/CONSTRAINTS.md | 486 ++++++++++++ .../skills/opendap/references/DATA-MODEL.md | 556 ++++++++++++++ .../skills/opendap/references/PROTOCOL.md | 345 +++++++++ windsurf-harnett/skills/pio/skill.md | 591 +++++++++++++++ windsurf-harnett/workflows/implement.md | 130 ++++ windsurf-harnett/workflows/issue.md | 210 ++++++ windsurf-harnett/workflows/release.md | 163 ++++ windsurf-harnett/workflows/review.md | 71 ++ windsurf-harnett/workflows/roadmap.md | 217 ++++++ windsurf-harnett/workflows/test.md | 103 +++ 26 files changed, 7912 insertions(+) create mode 100644 windsurf-harnett/rules/doc-check.md create mode 100644 windsurf-harnett/rules/local-build-command.md create mode 100644 windsurf-harnett/rules/slow-network.md create mode 100644 windsurf-harnett/skills/linkedin-posts.md create mode 100644 windsurf-harnett/skills/netcdf-architecture/README.md create mode 100644 windsurf-harnett/skills/netcdf-architecture/SKILL.md create mode 100644 windsurf-harnett/skills/netcdf-architecture/references/COMPONENTS.md create mode 100644 windsurf-harnett/skills/netcdf-architecture/references/DATA-STRUCTURES.md create mode 100644 windsurf-harnett/skills/netcdf-architecture/references/DISPATCH-TABLES.md create mode 100644 windsurf-harnett/skills/netcdf-architecture/references/EXAMPLES.md create mode 100644 windsurf-harnett/skills/netcdf-architecture/references/FORTRAN-INTERFACE.md create mode 100644 windsurf-harnett/skills/netcdf-architecture/references/UDF-PLUGINS.md create mode 100644 windsurf-harnett/skills/netcdf-java/skill.md create mode 100644 windsurf-harnett/skills/opendap/README.md create mode 100644 windsurf-harnett/skills/opendap/SKILL.md create mode 100644 windsurf-harnett/skills/opendap/references/CLIENT-USAGE.md create mode 100644 windsurf-harnett/skills/opendap/references/CONSTRAINTS.md create mode 100644 windsurf-harnett/skills/opendap/references/DATA-MODEL.md create mode 100644 windsurf-harnett/skills/opendap/references/PROTOCOL.md create mode 100644 windsurf-harnett/skills/pio/skill.md create mode 100644 windsurf-harnett/workflows/implement.md create mode 100644 windsurf-harnett/workflows/issue.md create mode 100644 windsurf-harnett/workflows/release.md create mode 100644 windsurf-harnett/workflows/review.md create mode 100644 windsurf-harnett/workflows/roadmap.md create mode 100644 windsurf-harnett/workflows/test.md diff --git a/windsurf-harnett/rules/doc-check.md b/windsurf-harnett/rules/doc-check.md new file mode 100644 index 000000000..57395d2bb --- /dev/null +++ b/windsurf-harnett/rules/doc-check.md @@ -0,0 +1,13 @@ +--- +trigger: always_on +--- + +# Documentation Check Rule +Before planning any code changes: +1. **Review Architecture & Design**: Check [docs/design.md](../../../docs/design.md) for system architecture, component interactions, and technical specifications. +2. **Verify Requirements**: Consult [docs/prd.md](../../../docs/prd.md) to ensure changes align with product requirements, API specifications, and feature definitions. +3. **Understand User Impact**: Read [docs/prfaq.md](../../../docs/prfaq.md) to consider how changes affect users, compatibility, and use cases. +4. **Check Version Compatibility**: Verify that changes maintain backward compatibility as specified in the documentation. +5. **Consider Format Support**: For changes affecting file formats (NetCDF, CDF, GeoTIFF), ensure compliance with format specifications in the documentation. +6. **Review Build Systems**: For build system changes, ensure both CMake and Autotools configurations are updated consistently. +7. **Update Documentation**: Plan to update relevant documentation if implementing new features or changing existing behavior. diff --git a/windsurf-harnett/rules/local-build-command.md b/windsurf-harnett/rules/local-build-command.md new file mode 100644 index 000000000..df75022fb --- /dev/null +++ b/windsurf-harnett/rules/local-build-command.md @@ -0,0 +1,68 @@ +--- +trigger: model_decision +--- +# Local Build Commands for NEP + +## When to Use This Rule +Use these paths for **local development builds on Ed's machine**. +For CI/GitHub Actions, different paths are used (see `.github/workflows/`). + +## Machine-Specific Dependency Paths +- **HDF5**: `/usr/local/hdf5-2.0.0/` +- **NetCDF-C**: `/usr/local/netcdf-c-4.10.0/` +- **NetCDF-Fortran**: `/usr/local/netcdf-fortran/` (if Fortran enabled) +- **CDF**: `/usr/local/cdf-3.9.1/` (if CDF enabled) +- **GeoTIFF**: System packages (`libgeotiff-dev`, `libtiff-dev`) + +## Runtime Environment +Before running tests or executables: +```bash +export LD_LIBRARY_PATH=/usr/local/hdf5-2.0.0/lib:/usr/local/netcdf-c-4.10.0/lib:/usr/local/netcdf-fortran/lib:/usr/local/cdf-3.9.1/lib:$LD_LIBRARY_PATH +``` + +## Build System Options + +### Autotools (Primary) +Working directory: `/home/ed/NEP` + +**Common configure flags:** +- `--enable-geotiff` - Enable GeoTIFF reader +- `--enable-cdf` - Enable NASA CDF reader +- `--disable-lz4` - Disable LZ4 compression +- `--disable-bzip2` - Disable bzip2 compression +- `--disable-fortran` - Disable Fortran wrapper library +- `--disable-shared` - Build static libraries only + +**Full build command:** +```bash +autoreconf -i && \ +CFLAGS="-g -O0" \ +CPPFLAGS="-I/usr/local/hdf5-2.0.0/include -I/usr/local/netcdf-c-4.10.0/include -I/usr/local/netcdf-fortran/include -I/usr/local/cdf-3.9.1/include" \ +LDFLAGS="-L/usr/local/hdf5-2.0.0/lib -L/usr/local/netcdf-c-4.10.0/lib -L/usr/local/netcdf-fortran/lib -L/usr/local/cdf-3.9.1/lib -Wl,-rpath,/usr/local/hdf5-2.0.0/lib -Wl,-rpath,/usr/local/netcdf-c-4.10.0/lib -Wl,-rpath,/usr/local/netcdf-fortran/lib" \ +./configure --enable-geotiff --enable-cdf --disable-fortran --disable-shared --disable-bzip2 --disable-lz4 && \ +make clean && make -j$(nproc) && make check +``` + +### CMake (Alternative) +**IMPORTANT**: All CMake builds must use the `build` directory, which is git-ignored. + +Working directory: `/home/ed/NEP` + +```bash +mkdir -p build && cd build +cmake .. \ + -DCMAKE_PREFIX_PATH="/usr/local/hdf5-1.14.6_cmake;/usr/local/netcdf-c-4.9.3_cmake;/usr/local/cdf-3.9.1" \ + -DCMAKE_BUILD_TYPE=Debug \ + -DENABLE_GEOTIFF=ON \ + -DENABLE_CDF=ON \ + -DENABLE_FORTRAN=OFF +make -j$(nproc) && ctest +``` + +**Never create CMake build artifacts outside the `build` directory** to avoid cluttering the repository with untracked files. + +## Troubleshooting +- **"library not found" errors**: Check `LD_LIBRARY_PATH` is set +- **"header not found" errors**: Verify `CPPFLAGS` includes correct paths +- **Link errors**: Ensure `LDFLAGS` includes all dependency lib directories +- **Test failures**: Run `make check VERBOSE=1` for detailed output diff --git a/windsurf-harnett/rules/slow-network.md b/windsurf-harnett/rules/slow-network.md new file mode 100644 index 000000000..0bb194941 --- /dev/null +++ b/windsurf-harnett/rules/slow-network.md @@ -0,0 +1,48 @@ +--- +trigger: always_on +--- + +# Network Resilience Guidelines + +The network here is slow and flakey. Follow these specific retry strategies: + +## HTTP Requests (read_url_content, search_web) +- **Retry count**: 3 attempts +- **Timeout**: 10 seconds per attempt +- **Backoff**: 5 seconds between retries +- **Pre-check**: Use `curl -I` for connectivity before full requests + +## MCP Server Calls +- **Retry count**: 5 attempts +- **Delay**: 2 seconds wait between attempts +- **Timeout**: 30 seconds per call +- **Pre-check**: Verify MCP server process is running + +## Git Operations +- **Retry count**: Immediate retry once +- **Issue**: Distinguish network vs authentication failures +- **Auth failures**: Don't retry - check credentials + +## Diagnostic Commands +- **Basic connectivity**: `ping -c 1 8.8.8.8` +- **HTTP test**: `curl -I https://github.com` +- **MCP status**: Check process list for MCP server + +## Failure Type Handling +- **Timeouts**: Retry with exponential backoff +- **Connection refused**: Check if service is running +- **Authentication errors**: Don't retry - fix credentials first +- **DNS failures**: Check `/etc/resolv.conf` and retry + +## Tool-Specific Guidance +- **bash network commands**: Always test connectivity first +- **file operations**: Local only - no retries needed +- **build commands**: Network-dependent parts need retry logic + +## GitHub Interactions +- **Prefer GitHub CLI**: Use `gh` command line tool instead of MCP GitHub tools +- **MCP GitHub tools**: Unreliable due to TLS handshake timeouts on slow network +- **Issue creation**: Use `gh issue create --title "..." --body "..."` or `gh issue create --body-file ` +- **PR operations**: Use `gh pr create`, `gh pr view`, `gh pr comment`, etc. +- **Authentication**: Ensure `gh auth status` shows valid credentials before operations +- **Fallback**: If `gh` unavailable, document the action needed and ask user to perform manually diff --git a/windsurf-harnett/skills/linkedin-posts.md b/windsurf-harnett/skills/linkedin-posts.md new file mode 100644 index 000000000..50f2e4a08 --- /dev/null +++ b/windsurf-harnett/skills/linkedin-posts.md @@ -0,0 +1,246 @@ +--- +description: Best practices for creating engaging and effective LinkedIn posts +--- + +# LinkedIn Post Creation Skills + +## My Audience Profile + +**Target Audience:** +- Engineers (software, systems, data) +- Earth scientists (geoscientists, climate scientists, environmental scientists) +- AI/ML engineers +- Scientific researchers and technical professionals + +**Content Approach:** +- Focus on the work and its benefits, not self-promotion +- Scientific community values substance over personal branding +- Emphasize technical contributions, research findings, and practical applications +- Highlight how the work advances the field or solves real problems +- Share methodologies, insights, and lessons learned +- Maintain professional, research-oriented tone +- Let the work speak for itself + +## Content Strategy + +### Know Your Audience +- Tailor content to resonate with your professional network +- Understand demographic characteristics (age, gender, location, education, income) +- Understand psychographic characteristics (personality, values, interests, lifestyle, motivations) +- Address topics relevant to your industry and audience's pain points + +### Content Types That Perform Well +- **Personal stories** of triumph and professional challenges +- **Educational content** - how-to guides, tips, industry insights +- **Case studies and research** - builds credibility and trust +- **Company updates** - focus on value to audience +- **List-style posts** - get more likes and comments +- **Behind-the-scenes** content - authentic look at your work +- **Industry news and trends** - positions you as thought leader + +## Writing Structure + +### Craft a Compelling Hook (First 2 Seconds Count) +- Start with attention-grabbing first sentence +- Use statistics, quotes, questions, or personal anecdotes +- Create intrigue to make readers click "See more" +- Best headline lengths: 40-49 characters +- Spend 50% of your time on the headline alone + +### Hook Techniques That Work +- Little-known facts +- Behind-the-scenes insights +- Catchy quotes +- Extraordinary insights +- Compelling statements +- Statistics +- Humor +- How-to offerings + +### Format for Readability +- **One post = one thesis** - stay focused +- Break walls of text into single-sentence paragraphs +- Use 3-4 hard paragraph breaks after headline to create intrigue +- Keep sentences short and conversational (14-year-old reading level) +- First 3 lines visible before "See more" - make them count +- Use emojis strategically to break up text and add personality +- Use bullet points or numbered lists for tips +- Leave white space between sections + +### Length Guidelines +- Keep it concise but impactful - every sentence must add value +- Longer posts (up to 15 lines) are acceptable on LinkedIn +- Only first 3 lines visible initially - use as teaser +- Reading online is 25% slower than print - be mindful + +## Engagement Tactics + +### Call-to-Action (CTA) +- Always include a clear CTA +- Tell audience exactly what to do next +- Options: comment, like, share, click link, answer question +- Specific instructions outperform vague endings +- Posts with engagement get prioritized by LinkedIn algorithm + +### Ask Questions +- End posts with questions to encourage commenting +- LinkedIn rewards posts with comments +- More comments = higher chance of trending +- Trending posts reach 2nd and 3rd-degree connections +- Question ideas: productivity tools, favorite quotes, career advice, industry tips + +### Tag and Mention +- @mention relevant connections or influencers +- Tag people who contributed to your story +- When they engage, post reaches their network +- Build relationships and give shoutouts +- Increases visibility exponentially + +## Visual Elements + +### When to Use Visuals +- Text-only posts often perform best on LinkedIn +- Add visuals when they enhance understanding +- Use charts/diagrams for data, trends, case studies +- Images of people work well (human psychology) +- Infographics for complex information +- Professional-looking visuals only + +### Video Content +- Videos are 20 times more shareable than other formats +- Keep videos short and professional +- Optimize for mobile viewing +- Video types that work: + - Brand/business origin stories + - How-to demonstrations + - Event previews and releases + - Expert interviews + - Educational lectures and talks + +## Hashtag Strategy + +### Best Practices +- Use 3-5 relevant hashtags maximum +- Place hashtags at the end of post +- Mix niche and well-known hashtags +- Create one branded hashtag for consistency +- Vary hashtags - don't repeat same ones (algorithm penalty) +- Research hashtags relevant to your topics + +### Popular Hashtags +- #entrepreneurship, #startups, #smallbusiness +- #marketing, #digitalmarketing, #branding +- #productivity, #motivation, #strategy +- #innovation, #hiringalert +- Industry-specific hashtags + +## Critical Don'ts + +### External Links +- **Never include external links in the post body** +- LinkedIn punishes posts with external links (low engagement) +- Instead: mention link is in comments section +- Ask connections to like the comment to keep it on top +- LinkedIn wants users to stay on platform + +### Other Mistakes to Avoid +- Don't use hashtags in headlines +- Don't overuse emojis (few strategic ones only) +- Don't bore your audience +- Don't use professional jargon - keep conversational +- Don't post without proofreading + +## Content Principles + +### Provide Value +- Share actionable insights, tips, strategies +- Offer industry-specific knowledge +- Provide time-saving hacks +- Address common challenges +- Position yourself as valuable resource + +### Be Authentic +- Share genuine experiences - successes and failures +- Be relatable and human +- Show personality (don't be afraid to be silly) +- Behind-the-scenes content works well +- Your unique experiences set you apart +- Human-to-human marketing resonates + +### Tell Stories +- Every story needs: problem, solution, moral +- Stories trigger emotions and sell +- Create sense of kinship and relatability +- Reflect human values +- Make stories unexpected yet relevant +- "Infotainment" - inform and entertain + +### Stay Timely and Relevant +- Discuss current events impacting your field +- Share insights on new technologies +- Comment on emerging trends +- Keep audience informed +- Establish yourself as thought leader + +## Posting Strategy + +### Timing and Consistency +- Post at the same time every day +- Consistency helps connections know when to expect content +- Schedule posts for maximum visibility +- Best times vary by audience - test and track + +### Experiment and Adapt +- Try different formats (lists, quotes, infographics, polls) +- Test what resonates with your audience +- Track which formats get most interaction +- Adapt future posts based on performance +- Don't delete underperforming posts - learn from them + +### Pre-Publish Checklist +- Review for grammatical errors +- Verify content relevance to professional goals +- Confirm message aligns with intended tone +- Check that it serves your audience +- Ensure authenticity +- Proofread thoroughly + +## Advanced Tactics + +### Offer Intellectual Property (IP) +- Share valuable resources with your community +- Examples: checklists, templates, how-to guides, scripts +- Based on your professional experience +- Positions you as expert +- Builds trust and authority + +### Use LinkedIn Features +- Polls for engagement +- Native video uploads (not external links) +- Document uploads for valuable resources +- LinkedIn articles for longer content + +### Algorithm Optimization +- Posts with good engagement get prioritized +- Comments matter more than likes +- Early engagement (first hour) is critical +- Avoid external links (algorithm penalty) +- Avoid repeating same hashtags (algorithm penalty) +- Plain text posts often outperform media posts + +## Key Metrics to Track +- Views and impressions +- Comments (most valuable) +- Likes and reactions +- Shares +- Click-through rates +- Profile visits from posts +- Connection requests from posts + +## Remember +- LinkedIn users come to grow professionally, not kill time +- Focus on educational, informative, relevant content +- Build genuine relationships through two-way conversations +- Consistency beats perfection +- Authenticity wins over polish +- Value to audience is paramount diff --git a/windsurf-harnett/skills/netcdf-architecture/README.md b/windsurf-harnett/skills/netcdf-architecture/README.md new file mode 100644 index 000000000..1fd153250 --- /dev/null +++ b/windsurf-harnett/skills/netcdf-architecture/README.md @@ -0,0 +1,59 @@ +# NetCDF-C Architecture Skill + +This Windsurf skill provides comprehensive knowledge of the NetCDF-C library architecture. + +## What This Skill Provides + +- **Dispatch table architecture** understanding +- **Format implementations** (NetCDF-3, HDF5, Zarr, DAP2, DAP4) +- **Data structures** used throughout the codebase +- **I/O layers** and storage backends +- **Component relationships** and data flow + +## Files + +- **SKILL.md** - Main skill file with architecture overview and quick reference +- **references/COMPONENTS.md** - Detailed component descriptions for each library +- **references/DATA-STRUCTURES.md** - Complete data structure documentation +- **references/DISPATCH-TABLES.md** - All dispatch table implementations + +## When to Use + +Use this skill when: +- Adding new features to NetCDF-C +- Debugging format-specific issues +- Understanding data flow through the library +- Implementing new dispatch tables or storage backends +- Working with metadata structures +- Investigating performance issues + +## Skill Compliance + +This skill follows the Windsurf Agent Skills specification: + +- ✅ Valid name: `netcdf-architecture` (lowercase, hyphens only) +- ✅ Description: Comprehensive, includes keywords and use cases +- ✅ SKILL.md: 371 lines (under 500 line recommendation) +- ✅ Progressive disclosure: Main content in SKILL.md, details in references/ +- ✅ Reference files: One level deep, focused topics +- ✅ Metadata: Author, version, date included + +## Installation + +To use this skill in Windsurf: + +1. Copy the `netcdf-architecture/` directory to your Windsurf skills location +2. The skill will be automatically detected and loaded +3. AI assistants can now access NetCDF-C architecture knowledge + +## Maintenance + +Update this skill when: +- New dispatch tables are added +- Major architectural changes occur +- New storage backends are implemented +- Data structures are significantly modified + +## Version + +Current version: 1.0 (January 14, 2026) diff --git a/windsurf-harnett/skills/netcdf-architecture/SKILL.md b/windsurf-harnett/skills/netcdf-architecture/SKILL.md new file mode 100644 index 000000000..0d06cc280 --- /dev/null +++ b/windsurf-harnett/skills/netcdf-architecture/SKILL.md @@ -0,0 +1,497 @@ +--- +name: netcdf-architecture +description: Understanding the NetCDF-C library architecture including dispatch tables, format implementations (NetCDF-3, HDF5, Zarr, DAP), I/O layers, and metadata structures. Use when working on NetCDF-C codebase, debugging format issues, adding new features, or understanding how different storage backends interact. +metadata: + author: netcdf-analysis + version: "1.0" + date: "2026-01-14" +--- + +# NetCDF-C Architecture Skill + +This skill provides comprehensive knowledge of the NetCDF-C library architecture to help you navigate, understand, and modify the codebase effectively. + +## Overview + +NetCDF-C is a multi-format I/O library built on a **dispatch table architecture** that provides a unified API across 7+ built-in storage formats plus 10 user-defined format (UDF) slots. The core design pattern uses function pointer tables to route operations to format-specific implementations. + +**Built-in formats**: NetCDF-3 (CDF-1/2/5), NetCDF-4/HDF5, Zarr, DAP2, DAP4 +**User-defined formats**: UDF0-UDF9 slots for custom format plugins + +## Core Architecture Pattern + +### Dispatch Table Design + +Every file format implements the same `NC_Dispatch` interface containing ~70 function pointers: + +```c +struct NC_Dispatch { + int model; // Format identifier + int dispatch_version; // Compatibility version + + // File operations + int (*create)(...); + int (*open)(...); + int (*close)(...); + + // Variable I/O + int (*get_vara)(...); + int (*put_vara)(...); + + // Metadata operations + int (*def_dim)(...); + int (*def_var)(...); + int (*put_att)(...); + + // ... ~60 more function pointers +}; +``` + +**Location**: `include/netcdf_dispatch.h` + +### Common File Handle (NC Structure) + +Every open file is represented by an `NC` struct: + +```c +typedef struct NC { + int ext_ncid; // External ID (user-visible) + int int_ncid; // Internal ID (format-specific) + const NC_Dispatch* dispatch; // Function pointer table + void* dispatchdata; // Format-specific metadata + char* path; // File path + int mode; // Open mode flags +} NC; +``` + +**Location**: `include/nc.h` + +## Directory Structure + +### Primary Libraries + +- **`libdispatch/`** - Central routing layer, API entry points, utilities, UDF plugin loading +- **`libsrc/`** - Classic NetCDF-3 implementation (CDF-1, CDF-2, CDF-5) +- **`libsrc4/`** - NetCDF-4 enhanced model coordination +- **`libhdf5/`** - HDF5 storage backend +- **`libnczarr/`** - Zarr cloud-native storage +- **`libdap2/`** + **`oc2/`** - OPeNDAP DAP2 client +- **`libdap4/`** - OPeNDAP DAP4 client +- **`libhdf4/`** - HDF4 file access (optional) +- **User plugins** - External shared libraries for UDF0-UDF9 slots + +### Support Libraries + +- **`include/`** - Public API headers and internal interfaces +- **`libncpoco/`** - Portable components +- **`libncxml/`** - XML parsing for DAP4 +- **`liblib/`** - Additional utilities + +## Key Components by Library + +### libdispatch/ - The Routing Layer + +**Purpose**: Provides unified API facade and routes calls to appropriate format implementations. + +**Critical Files**: +- `ddispatch.c` - Dispatch initialization, global state management +- `dfile.c` - File open/create orchestration, format detection +- `dvarget.c`, `dvarput.c` - Variable I/O entry points +- `dvar.c`, `datt.c`, `ddim.c` - Metadata operation entry points +- `dinfermodel.c` - Format detection (magic numbers, URLs) + +**Format Detection Logic**: +1. Check magic number (first 8 bytes) - includes user-defined magic numbers +2. Parse URL scheme (http://, s3://, file://) +3. Analyze mode flags (NC_NETCDF4, NC_CLASSIC_MODEL, NC_UDF0-NC_UDF9, etc.) +4. Select appropriate dispatch table (built-in or user-defined) + +**Utilities**: +- `ncjson.c` - JSON parsing +- `ncuri.c` - URI parsing +- `dauth.c` - Authentication (includes RC file parsing for UDF configuration) +- `dhttp.c` - HTTP operations +- `ds3util.c` - S3/cloud utilities +- `drc.c` - RC file parsing for UDF plugin configuration +- `dutil.c` - Plugin loading (dlopen/LoadLibrary) + +### libsrc/ - Classic NetCDF-3 + +**Purpose**: Implements traditional binary NetCDF formats. + +**Dispatch Table**: `NC3_dispatcher` in `nc3dispatch.c` + +**Metadata Structure**: `NC3_INFO` - Simple arrays with hashmaps + +**Critical Files**: +- `nc3dispatch.c` (517 lines) - Dispatch table implementation +- `nc3internal.c` - Metadata management +- `ncx.c` (743KB) - XDR-like encoding/decoding for all data types +- `putget.c` (353KB) - Variable I/O operations +- `attr.c` (47KB) - Attribute operations +- `var.c`, `dim.c` - Variable and dimension management + +**I/O Abstraction (ncio layer)**: +- `posixio.c` - Standard POSIX file I/O +- `memio.c` - In-memory files +- `httpio.c` - HTTP byte-range access +- `s3io.c` - S3 object storage + +**Data Structures**: +```c +typedef struct NC3_INFO { + NC_dimarray dims; // Dimensions + NC_attrarray attrs; // Global attributes + NC_vararray vars; // Variables + size_t xsz; // External size + size_t begin_var; // Offset to variables + size_t begin_rec; // Offset to record data + size_t recsize; // Record size + // ... more fields +} NC3_INFO; +``` + +### libsrc4/ - NetCDF-4 Coordination + +**Purpose**: Thin coordination layer for NetCDF-4 enhanced features (groups, user-defined types). + +**Note**: This is NOT a complete implementation - it delegates to HDF5 or Zarr backends. + +**Files**: +- `nc4dispatch.c` - Minimal initialization +- `nc4attr.c`, `nc4dim.c`, `nc4var.c` - Enhanced metadata operations +- `nc4grp.c` - Group operations +- `nc4type.c` - User-defined type operations +- `nc4internal.c` - Common infrastructure + +### libhdf5/ - HDF5 Storage Backend + +**Purpose**: Implements NetCDF-4 using HDF5 as the storage format. + +**Dispatch Table**: `HDF5_dispatcher` in `hdf5dispatch.c` + +**Metadata Structure**: `NC_FILE_INFO_T` with hierarchical groups + +**Critical Files**: +- `hdf5dispatch.c` (152 lines) - Dispatch table +- `nc4hdf.c` (87KB) - Core HDF5 integration +- `hdf5open.c` (99KB) - File opening, metadata reading from HDF5 +- `hdf5var.c` (85KB) - Variable I/O with chunking, compression, filters +- `hdf5attr.c` (28KB) - Attribute operations +- `hdf5filter.c` - Filter/compression plugin management +- `H5FDhttp.c` - HTTP virtual file driver for byte-range access + +**Key Data Structures**: +```c +typedef struct NC_FILE_INFO_T { + NC_GRP_INFO_T* root_grp; // Root group + int no_write; // Read-only flag + void* format_file_info; // HDF5-specific data + // ... more fields +} NC_FILE_INFO_T; + +typedef struct NC_VAR_INFO_T { + NC_OBJ hdr; // Name and ID + NC_GRP_INFO_T* container; // Parent group + size_t ndims; // Number of dimensions + int* dimids; // Dimension IDs + size_t* chunksizes; // Chunk sizes + int storage; // Chunked/contiguous/compact + int endianness; // Byte order + void* filters; // Compression filters + // ... more fields +} NC_VAR_INFO_T; +``` + +**Delegates to**: HDF5 library → HDF5 VFD layer → actual storage + +### libnczarr/ - Zarr Storage + +**Purpose**: Cloud-native storage using Zarr format specification. + +**Dispatch Table**: `NCZ_dispatcher` in `zdispatch.c` + +**Metadata Structure**: `NC_FILE_INFO_T` (same as HDF5) + +**Critical Files**: +- `zdispatch.c` (323 lines) - Dispatch table +- `zarr.c` - Main Zarr implementation +- `zsync.c` (84KB) - Data synchronization, chunk management +- `zvar.c` (76KB) - Variable operations +- `zfilter.c` - Codec pipeline (compression, filters) +- `zxcache.c` - Chunk caching + +**Storage Abstraction (zmap)**: +- `zmap.c` - Abstract storage interface +- `zmap_file.c` - Filesystem backend +- `zmap_s3sdk.c` - AWS S3 backend +- `zmap_zip.c` - ZIP archive backend + +**Key Feature**: JSON metadata (.zarray, .zgroup, .zattrs files) + +### libdap2/ + oc2/ - OPeNDAP DAP2 Client + +**Purpose**: Access remote OPeNDAP servers using DAP2 protocol. + +**Dispatch Table**: `NCD2_dispatcher` in `ncd2dispatch.c` + +**Components**: +- `ncd2dispatch.c` (85KB) - Dispatch implementation +- `getvara.c` (44KB) - Maps NetCDF API to DAP requests +- `constraints.c` - DAP constraint expression handling +- `cache.c` - Response caching + +**OC2 Library** (OPeNDAP Client in `oc2/`): +- `oc.c` (62KB) - Main client implementation +- `dapparse.c`, `daplex.c` - DDS/DAS parsing +- `ocdata.c` - Data retrieval and decoding +- `occurlfunctions.c` - HTTP/libcurl integration + +### libdap4/ - OPeNDAP DAP4 Client + +**Purpose**: Access remote DAP4 servers (newer protocol). + +**Dispatch Table**: `NCD4_dispatcher` in `ncd4dispatch.c` + +**Critical Files**: +- `ncd4dispatch.c` (24KB) - Dispatch table +- `d4parser.c` (49KB) - DMR (Dataset Metadata Response) parsing +- `d4data.c` - Binary data handling +- `d4chunk.c` - Chunked response processing +- `d4meta.c` (34KB) - Metadata translation to NetCDF model +- `d4curlfunctions.c` - HTTP operations + +### User-Defined Formats (UDFs) + +**Purpose**: Extensible plugin system for custom file formats and storage backends. + +**Available Slots**: UDF0 through UDF9 (10 independent format slots) + +**Dispatch Tables**: Registered via `nc_def_user_format()` or RC file configuration + +**Key Features**: +- **Plugin loading**: Automatic loading from RC files during `nc_initialize()` +- **Magic number detection**: Optional automatic format detection +- **Shared libraries**: .so (Unix) or .dll (Windows) plugins +- **Full API support**: Plugins implement complete `NC_Dispatch` interface + +**Plugin Architecture**: + +1. **Dispatch Table**: Plugin provides `NC_Dispatch` structure with function pointers +2. **Initialization Function**: Exported function called during plugin load +3. **Format-Specific Code**: Custom implementation of file I/O and data operations + +**Registration Methods**: + +**Programmatic Registration**: +```c +// Register UDF in slot 0 with magic number +nc_def_user_format(NC_UDF0 | NC_NETCDF4, &my_dispatcher, "MYFORMAT"); + +// Query registered UDF +NC_Dispatch *disp; +nc_inq_user_format(NC_UDF0, &disp, magic_buffer); +``` + +**RC File Configuration** (`.ncrc`): +```ini +NETCDF.UDF0.LIBRARY=/usr/local/lib/libmyformat.so +NETCDF.UDF0.INIT=myformat_init +NETCDF.UDF0.MAGIC=MYFORMAT +``` + +**Plugin Loading Process**: +1. RC files parsed during `nc_initialize()` +2. Library loaded via `dlopen()` (Unix) or `LoadLibrary()` (Windows) +3. Init function located via `dlsym()` or `GetProcAddress()` +4. Init function calls `nc_def_user_format()` to register dispatch table +5. Dispatch table ABI version verified (`NC_DISPATCH_VERSION`) +6. Plugin remains loaded for process lifetime + +**RC File Search Order**: +1. `$HOME/.ncrc` +2. `$HOME/.daprc` +3. `$HOME/.dodsrc` +4. `$CWD/.ncrc` +5. `$CWD/.daprc` +6. `$CWD/.dodsrc` + +**UDF Slot Modes**: +- **UDF0, UDF1**: Original slots, mode flags in lower 16 bits +- **UDF2-UDF9**: Extended slots, mode flags in upper 16 bits + +**Pre-defined Dispatch Functions** (for plugin use): +- `NC_RO_*` - Read-only stubs (return `NC_EPERM`) +- `NC_NOTNC4_*` - Not-NetCDF-4 stubs (return `NC_ENOTNC4`) +- `NC_NOTNC3_*` - Not-NetCDF-3 stubs (return `NC_ENOTNC3`) +- `NC_NOOP_*` - No-operation stubs (return `NC_NOERR`) +- `NCDEFAULT_*` - Generic implementations +- `NC4_*` - NetCDF-4 inquiry functions using internal metadata model + +**Critical Files**: +- `libdispatch/dfile.c` - UDF dispatch table storage (`UDF0_dispatch_table`, etc.) +- `libdispatch/ddispatch.c` - `nc_def_user_format()`, `nc_inq_user_format()` +- `libdispatch/drc.c` - RC file parsing for UDF configuration +- `libdispatch/dutil.c` - Plugin library loading +- `include/netcdf_dispatch.h` - `NC_Dispatch` structure definition +- `libdispatch/dreadonly.c` - Pre-defined read-only stubs +- `libdispatch/dnotnc*.c` - Pre-defined not-supported stubs + +**Example Plugin Structure**: +```c +#include "netcdf_dispatch.h" + +static NC_Dispatch my_dispatcher = { + NC_FORMATX_UDF0, // Use UDF slot 0 + NC_DISPATCH_VERSION, // Current ABI version + + NC_RO_create, // Read-only: use predefined function + my_open, // Custom open function + my_close, // Custom close function + NC4_inq, // Use NC4 inquiry defaults + // ... ~70 function pointers total +}; + +// Initialization function - must be exported +int my_plugin_init(void) { + return nc_def_user_format(NC_UDF0 | NC_NETCDF4, + &my_dispatcher, + "MYFMT"); +} +``` + +**Security Considerations**: +- RC files must specify absolute library paths +- Plugins execute arbitrary code in process space +- Only load trusted libraries +- Library verifies dispatch table ABI version + +**Common Use Cases**: +- Proprietary or specialized file formats +- Custom storage backends +- Format translation layers +- Domain-specific data formats +- Integration with legacy systems + +## Common Patterns + +### 1. API Call Flow + +``` +User calls nc_get_vara(ncid, varid, start, count, data) + ↓ +libdispatch/dvarget.c + ↓ +Lookup NC* from ncid → get dispatch table + ↓ +dispatch->get_vara(...) + ↓ +Format-specific implementation: + • NC3_get_vara() → ncx.c XDR decode → ncio read + • NC4_get_vara() → HDF5 API → chunk cache → decompress + • NCZ_get_vara() → zmap retrieve → codec pipeline + • NCD2_get_vara() → HTTP request → parse DDS/DAS +``` + +### 2. File Opening + +``` +nc_open(path, mode, &ncid) + ↓ +libdispatch/dfile.c: NC_open() + ↓ +dinfermodel.c: Detect format + • Check magic number + • Parse URL scheme + • Analyze mode flags + ↓ +Select dispatch table + ↓ +dispatch->open(path, mode, ...) + ↓ +Format-specific open implementation + ↓ +Return ncid to user +``` + +### 3. Metadata Access + +All formats use indexed structures for fast lookup: +- **NC3**: Arrays with `NC_hashmap` +- **NC4/HDF5/Zarr**: `NCindex` (hash-based index) + +## Important Headers + +### Public API +- `netcdf.h` - Main public API +- `netcdf_par.h` - Parallel I/O extensions +- `netcdf_filter.h` - Filter API +- `netcdf_mem.h` - In-memory file API + +### Internal Interfaces +- `ncdispatch.h` - Dispatch layer interfaces +- `netcdf_dispatch.h` - NC_Dispatch structure definition +- `nc.h` - NC structure and common functions +- `nc3internal.h` - NetCDF-3 internal structures +- `nc4internal.h` - NetCDF-4 internal structures +- `nc3dispatch.h`, `nc4dispatch.h`, `hdf5dispatch.h` - Format-specific dispatch headers + +## When to Use This Skill + +Use this skill when: +- **Adding new features** to NetCDF-C +- **Debugging format-specific issues** (e.g., HDF5 vs Zarr differences) +- **Understanding data flow** through the library +- **Implementing new dispatch tables** or storage backends +- **Developing UDF plugins** for custom file formats +- **Modifying I/O operations** (chunking, compression, filters) +- **Working with metadata structures** (groups, types, dimensions) +- **Investigating performance issues** (caching, I/O patterns) +- **Integrating new protocols** (new remote access methods) +- **Extending NetCDF-C** with proprietary or domain-specific formats + +## Quick Reference + +### Find the Right File + +**For API entry points**: Look in `libdispatch/d*.c` +**For NetCDF-3 operations**: Look in `libsrc/` +**For HDF5 operations**: Look in `libhdf5/` +**For Zarr operations**: Look in `libnczarr/` +**For remote access**: Look in `libdap2/` or `libdap4/` +**For data encoding**: Look in `libsrc/ncx.c` +**For I/O backends**: Look in `libsrc/*io.c` or `libnczarr/zmap*.c` + +### Common Tasks + +**Adding a new API function**: +1. Add to `include/netcdf.h` +2. Add entry point in `libdispatch/` +3. Add to `NC_Dispatch` structure +4. Implement in each format's dispatch table + +**Adding a new format**: +1. Create new library directory +2. Implement `NC_Dispatch` table +3. Register in `libdispatch/ddispatch.c` +4. Add format detection logic + +**Debugging I/O issues**: +1. Enable logging: `export NETCDF_LOG_LEVEL=5` +2. Check dispatch table selection +3. Trace through format-specific implementation +4. Check I/O layer (ncio, HDF5 VFD, zmap) + +## Additional Resources + +See [references/COMPONENTS.md](references/COMPONENTS.md) for detailed component descriptions. + +See [references/DATA-STRUCTURES.md](references/DATA-STRUCTURES.md) for complete data structure documentation. + +See [references/DISPATCH-TABLES.md](references/DISPATCH-TABLES.md) for all dispatch table implementations. + +See [references/UDF-PLUGINS.md](references/UDF-PLUGINS.md) for comprehensive UDF plugin development guide. + +See [references/EXAMPLES.md](references/EXAMPLES.md) for programming examples and common patterns. + +See [references/FORTRAN-INTERFACE.md](references/FORTRAN-INTERFACE.md) for NetCDF Fortran 90 API documentation and usage patterns. diff --git a/windsurf-harnett/skills/netcdf-architecture/references/COMPONENTS.md b/windsurf-harnett/skills/netcdf-architecture/references/COMPONENTS.md new file mode 100644 index 000000000..564e65779 --- /dev/null +++ b/windsurf-harnett/skills/netcdf-architecture/references/COMPONENTS.md @@ -0,0 +1,461 @@ +# NetCDF-C Component Details + +This reference provides detailed information about each major component in the NetCDF-C library. + +## libdispatch/ - Central Routing Layer + +### Core Files + +**ddispatch.c** (477 lines) +- Global state management (`NCglobalstate`) +- Dispatch initialization (`NCDISPATCH_initialize()`, `NCDISPATCH_finalize()`) +- Atomic type utilities +- Alignment configuration +- Manages: temp directories, home directory, RC files, chunk cache defaults + +**dfile.c** (2225 lines) +- File open/create orchestration +- User-defined format registration (`nc_def_user_format()`) +- Format detection coordination +- NC structure lifecycle management + +**dinfermodel.c** (46KB) +- Format detection from magic numbers +- URL scheme parsing (http://, https://, s3://, file://) +- Mode flag analysis +- Dispatch table selection logic + +**dvarget.c / dvarput.c** +- Variable I/O entry points +- Type conversion coordination +- Stride/index calculation +- Delegates to format-specific `get_vara()` / `put_vara()` + +**dvar.c, datt.c, ddim.c, dgroup.c, dtype.c** +- Metadata operation entry points +- Validation and error checking +- Delegation to format-specific implementations + +### Utility Files + +**ncjson.c** (35KB) - JSON parsing for Zarr metadata +**ncuri.c** (35KB) - URI parsing and manipulation +**ncbytes.c** - Dynamic byte buffer management +**dauth.c** (12KB) - Authentication (AWS, bearer tokens) +**dhttp.c** (25KB) - HTTP operations via libcurl +**ds3util.c** (32KB) - S3/cloud storage utilities +**nclist.c** - Dynamic list data structure +**nchashmap.c** (149KB) - Hash map implementation +**ncindex.c** - Index structure for fast metadata lookup + +## libsrc/ - Classic NetCDF-3 + +### Format Support +- CDF-1: Classic format (32-bit offsets) +- CDF-2: 64-bit offset format +- CDF-5: 64-bit data format (large variables) + +### Core Implementation + +**nc3dispatch.c** (517 lines) +- `NC3_dispatcher` table with ~70 function pointers +- Implements all required dispatch operations +- Returns `NC_ENOTNC4` for NetCDF-4-only features +- Stubs for groups, user-defined types, compression + +**nc3internal.c** (1784 lines) +- `NC3_INFO` structure management +- Metadata lifecycle (create, duplicate, free) +- Type checking (`nc3_cktype()`) +- Format version handling + +**ncx.c** (743KB - generated from ncx.m4) +- External Data Representation (XDR-like) +- Encoding/decoding for all atomic types +- Byte swapping for endianness +- Padding and alignment +- Platform-specific optimizations + +**putget.c** (353KB - generated from putget.m4) +- Variable data I/O operations +- Type conversion matrix (all type combinations) +- Strided access implementation +- Record variable handling +- Fill value management + +**attr.c** (47KB - generated from attr.m4) +- Attribute CRUD operations +- Attribute type conversion +- Global vs variable attributes +- Attribute renaming and deletion + +**var.c** (18KB) +- Variable definition and inquiry +- Variable renaming +- Coordinate variable detection +- Record variable management + +**dim.c** (10KB) +- Dimension definition and inquiry +- Unlimited dimension handling +- Dimension renaming + +### I/O Layer (ncio abstraction) + +**ncio.h / ncio.c** +- Abstract I/O interface +- Strategy pattern for different backends +- Buffer management + +**posixio.c** (45KB) +- POSIX file I/O (open, read, write, seek) +- Memory-mapped I/O option +- File locking +- Platform-specific optimizations + +**memio.c** (20KB) +- In-memory file implementation +- Dynamic buffer growth +- Extract to external memory + +**httpio.c** (8KB) +- HTTP byte-range requests +- Read-only access +- Caching strategy + +**s3io.c** (8KB) +- S3 object storage access +- Byte-range requests +- AWS SDK integration + +### Data Structures + +```c +typedef struct NC3_INFO { + size_t chunk; // Chunk size hint + size_t xsz; // External file size + size_t begin_var; // Offset to non-record variables + size_t begin_rec; // Offset to record variables + size_t recsize; // Size of one record + size_t numrecs; // Number of records + NC_dimarray dims; // Dimensions + NC_attrarray attrs; // Global attributes + NC_vararray vars; // Variables + ncio* nciop; // I/O provider + int flags; // File flags + // ... more fields +} NC3_INFO; + +typedef struct NC_var { + NC_string* name; // Variable name + size_t ndims; // Number of dimensions + int* dimids; // Dimension IDs + NC_attrarray attrs; // Variable attributes + nc_type type; // Data type + size_t len; // Product of dimension sizes + size_t begin; // Offset in file + // ... more fields +} NC_var; +``` + +## libhdf5/ - HDF5 Storage Backend + +### Core Files + +**hdf5dispatch.c** (152 lines) +- `HDF5_dispatcher` table +- Initialization/finalization +- HTTP VFD registration + +**nc4hdf.c** (87KB) +- Core HDF5 integration +- File creation with HDF5 API +- Metadata synchronization +- Group/dataset creation +- Attribute handling + +**hdf5open.c** (99KB) +- File opening logic +- HDF5 metadata reading +- Dimension scale detection +- Coordinate variable identification +- User-defined type reconstruction + +**hdf5var.c** (85KB) +- Variable I/O operations +- Chunking coordination with HDF5 +- Filter pipeline management +- Type conversion +- Parallel I/O support + +**hdf5attr.c** (28KB) +- Attribute operations via HDF5 +- Reserved attribute handling (_NCProperties, etc.) +- Type conversion for attributes + +**hdf5filter.c** (16KB) +- Filter plugin management +- HDF5 filter pipeline integration +- Compression (deflate, szip, etc.) +- Custom filter registration + +**hdf5dim.c, hdf5grp.c, hdf5type.c** +- Dimension, group, and type operations +- HDF5 dimension scales +- Group hierarchy management +- User-defined type translation + +**H5FDhttp.c** (28KB) +- HTTP Virtual File Driver +- Byte-range request support +- Read-only remote access +- Caching strategy + +### Key Features +- Uses HDF5 dimension scales for dimensions +- Stores NetCDF metadata in HDF5 attributes +- Supports chunking, compression, filters +- Parallel I/O via HDF5 parallel features +- Backward compatible with pure HDF5 files (with limitations) + +## libnczarr/ - Zarr Storage + +### Core Files + +**zdispatch.c** (323 lines) +- `NCZ_dispatcher` table +- Many operations delegate to libsrc4 +- Zarr-specific implementations for I/O and metadata + +**zarr.c** (8KB) +- Main Zarr format implementation +- Format version handling +- Metadata JSON generation + +**zsync.c** (84KB) +- Data synchronization between memory and storage +- Chunk reading/writing +- Metadata persistence (.zarray, .zgroup, .zattrs) +- Cache management + +**zvar.c** (76KB) +- Variable operations +- Chunk coordinate calculation +- Data assembly from chunks +- Fill value handling + +**zfilter.c** (37KB) +- Codec pipeline implementation +- Compression (blosc, zlib, etc.) +- Filter chaining +- Plugin support + +**zxcache.c** (27KB) +- Chunk cache implementation +- LRU eviction +- Dirty chunk tracking +- Write-back strategy + +### Storage Abstraction (zmap) + +**zmap.c** (11KB) +- Abstract storage interface +- Key-value semantics +- Backend selection + +**zmap_file.c** (31KB) +- Filesystem backend +- Directory structure (.zarray, .zgroup files) +- Atomic writes + +**zmap_s3sdk.c** (15KB) +- AWS S3 backend +- Object storage operations +- Credential management + +**zmap_zip.c** (22KB) +- ZIP archive backend +- Read-only access +- Efficient random access + +### Zarr Format Details +- JSON metadata (.zarray, .zgroup, .zattrs) +- Chunked storage (one file per chunk) +- Codec pipeline (compression, filters) +- Dimension separator (. or /) +- V2 and V3 format support (partial) + +## libdap2/ + oc2/ - DAP2 Client + +### libdap2/ Files + +**ncd2dispatch.c** (85KB) +- `NCD2_dispatcher` table +- Complete dispatch implementation +- Constraint handling integration + +**getvara.c** (44KB) +- Maps NetCDF API to DAP requests +- Constraint expression generation +- Subsetting and striding +- Type conversion + +**constraints.c** (25KB) +- DAP constraint expression parsing +- Projection and selection +- Optimization + +**cache.c** (13KB) +- HTTP response caching +- Cache invalidation +- Disk-based cache + +### oc2/ - OPeNDAP Client Library + +**oc.c** (62KB) +- Main client implementation +- Connection management +- Request/response handling +- Error handling + +**dapparse.c / daplex.c** +- DDS (Dataset Descriptor Structure) parsing +- DAS (Dataset Attribute Structure) parsing +- Lexical analysis + +**ocdata.c** (10KB) +- Binary data decoding +- XDR stream processing +- Data assembly + +**occurlfunctions.c** (9KB) +- libcurl integration +- HTTP request building +- Authentication +- SSL/TLS support + +### DAP2 Protocol +- DDS: Describes data structure +- DAS: Describes attributes +- DODS: Binary data response +- Constraint expressions for subsetting + +## libdap4/ - DAP4 Client + +### Core Files + +**ncd4dispatch.c** (24KB) +- `NCD4_dispatcher` table +- DAP4-specific operations + +**d4parser.c** (49KB) +- DMR (Dataset Metadata Response) parsing +- XML-based metadata +- Namespace handling + +**d4data.c** (14KB) +- Binary data handling +- Checksum verification +- Data assembly + +**d4chunk.c** (6KB) +- Chunked response processing +- Streaming data support + +**d4meta.c** (34KB) +- Metadata translation to NetCDF model +- Group hierarchy construction +- Type mapping + +**d4curlfunctions.c** (15KB) +- HTTP operations +- Authentication +- Error handling + +### DAP4 Protocol +- DMR: XML metadata response +- Binary data with checksums +- Chunked transfer encoding +- Enhanced type system + +## Support Libraries + +### libncpoco/ +Portable components for cross-platform compatibility + +### libncxml/ +XML parsing for DAP4 DMR responses + +### liblib/ +Additional utility code and compatibility layers + +## Global State (NCglobalstate) + +Located in `libdispatch/ddispatch.c`: + +```c +typedef struct NCglobalstate { + char* tempdir; // Temporary directory + char* home; // Home directory + char* cwd; // Current working directory + + struct NCRCinfo* rcinfo; // RC file configuration + + struct { + size_t size; // Chunk cache size + size_t nelems; // Number of elements + float preemption; // Preemption policy + } chunkcache; + + struct { + int threshold; // Alignment threshold + int alignment; // Alignment value + int defined; // Whether set + } alignment; + + struct { + char* default_region; // AWS region + char* config_file; // AWS config file + char* profile; // AWS profile + char* access_key_id; // Access key + char* secret_access_key; // Secret key + } aws; + + NClist* pluginpaths; // Filter plugin paths +} NCglobalstate; +``` + +## Filter/Codec System + +### HDF5 Filters +- Standard: deflate, shuffle, fletcher32, szip +- Plugin system for custom filters +- Filter IDs registered with HDF Group +- Parameters passed as unsigned int arrays + +### Zarr Codecs +- JSON-based codec configuration +- Codec chain (multiple codecs) +- Standard: blosc, zlib, gzip, lz4, zstd +- Extensible through plugins + +## Parallel I/O Support + +### HDF5 Parallel +- Uses MPI-IO via HDF5 +- Collective and independent I/O +- Requires parallel HDF5 build + +### PnetCDF (libsrcp/) +- Experimental parallel I/O for NetCDF-3 +- MPI-IO based +- Separate dispatch table + +## Testing Structure + +- `nc_test/` - NetCDF-3 tests +- `nc_test4/` - NetCDF-4/HDF5 tests +- `nczarr_test/` - Zarr tests +- `ncdap_test/` - DAP2 tests +- `unit_test/` - Unit tests +- `h5_test/` - HDF5 interoperability tests diff --git a/windsurf-harnett/skills/netcdf-architecture/references/DATA-STRUCTURES.md b/windsurf-harnett/skills/netcdf-architecture/references/DATA-STRUCTURES.md new file mode 100644 index 000000000..92a3cda71 --- /dev/null +++ b/windsurf-harnett/skills/netcdf-architecture/references/DATA-STRUCTURES.md @@ -0,0 +1,482 @@ +# NetCDF-C Data Structures Reference + +This reference documents the key data structures used throughout NetCDF-C. + +## Common Structures + +### NC - File Handle (nc.h) + +```c +typedef struct NC { + int ext_ncid; // External ID (user-visible) + int int_ncid; // Internal ID (format-specific) + const struct NC_Dispatch* dispatch; // Function pointer table + void* dispatchdata; // Format-specific metadata + char* path; // File path + int mode; // Open mode flags +} NC; +``` + +**Location**: `include/nc.h:23-30` + +**Purpose**: Common handle for all open files, regardless of format. + +**Key Fields**: +- `ext_ncid`: The ID returned to users, managed globally +- `int_ncid`: Format-specific ID (e.g., NetCDF-3 has its own ID space) +- `dispatch`: Points to format-specific function table +- `dispatchdata`: Points to `NC3_INFO`, `NC_FILE_INFO_T`, etc. + +### NC_Dispatch - Function Pointer Table (netcdf_dispatch.h) + +```c +struct NC_Dispatch { + int model; // NC_FORMATX_NC3, NC_FORMATX_NC4, etc. + int dispatch_version; // Must match NC_DISPATCH_VERSION + + // File operations + int (*create)(const char *path, int cmode, ...); + int (*open)(const char *path, int mode, ...); + int (*redef)(int); + int (*_enddef)(int, size_t, size_t, size_t, size_t); + int (*sync)(int); + int (*abort)(int); + int (*close)(int, void *); + + // Metadata operations + int (*def_dim)(int, const char *, size_t, int *); + int (*def_var)(int, const char *, nc_type, int, const int *, int *); + int (*put_att)(int, int, const char *, nc_type, size_t, const void *, nc_type); + + // Variable I/O + int (*get_vara)(int, int, const size_t *, const size_t *, void *, nc_type); + int (*put_vara)(int, int, const size_t *, const size_t *, const void *, nc_type); + + // ... ~60 more function pointers +}; +``` + +**Location**: `include/netcdf_dispatch.h:34-256` + +**Implementations**: +- `NC3_dispatcher` - NetCDF-3 (libsrc/nc3dispatch.c) +- `HDF5_dispatcher` - HDF5 (libhdf5/hdf5dispatch.c) +- `NCZ_dispatcher` - Zarr (libnczarr/zdispatch.c) +- `NCD2_dispatcher` - DAP2 (libdap2/ncd2dispatch.c) +- `NCD4_dispatcher` - DAP4 (libdap4/ncd4dispatch.c) + +## NetCDF-3 Structures (libsrc) + +### NC3_INFO - NetCDF-3 File Metadata + +```c +typedef struct NC3_INFO { + size_t chunk; // Chunk size hint for I/O + size_t xsz; // External file size + size_t begin_var; // Offset to non-record variables + size_t begin_rec; // Offset to record variables + size_t recsize; // Size of one record + size_t numrecs; // Number of records written + + NC_dimarray dims; // Dimensions + NC_attrarray attrs; // Global attributes + NC_vararray vars; // Variables + + ncio* nciop; // I/O provider + int flags; // File flags + int old_format; // CDF-1, CDF-2, or CDF-5 +} NC3_INFO; +``` + +**Location**: Defined across `include/nc3internal.h` + +### NC_dim - Dimension + +```c +typedef struct { + NC_string* name; // Dimension name + size_t size; // Dimension length (NC_UNLIMITED for unlimited) +} NC_dim; +``` + +### NC_var - Variable + +```c +typedef struct NC_var { + NC_string* name; // Variable name + size_t ndims; // Number of dimensions + int* dimids; // Dimension IDs + NC_attrarray attrs; // Variable attributes + nc_type type; // Data type + size_t len; // Product of dimension sizes + size_t begin; // Offset in file + + // For record variables + size_t* shape; // Cached dimension sizes + size_t* dsizes; // Cached dimension products +} NC_var; +``` + +### NC_attr - Attribute + +```c +typedef struct NC_attr { + NC_string* name; // Attribute name + nc_type type; // Data type + size_t nelems; // Number of elements + void* xvalue; // Attribute value (external representation) +} NC_attr; +``` + +### NC_dimarray, NC_vararray, NC_attrarray + +```c +typedef struct NC_dimarray { + size_t nalloc; // Allocated size + size_t nelems; // Number of elements + NC_hashmap* hashmap; // For fast name lookup + NC_dim** value; // Array of dimension pointers +} NC_dimarray; + +// Similar for NC_vararray and NC_attrarray +``` + +**Key Feature**: Hash maps for O(1) name lookup + +## NetCDF-4 Structures (libsrc4, libhdf5, libnczarr) + +### NC_FILE_INFO_T - File Metadata + +```c +typedef struct NC_FILE_INFO_T { + NC_GRP_INFO_T* root_grp; // Root group + int no_write; // Read-only flag + int ignore_att_convention; // Ignore _NCProperties + void* format_file_info; // Format-specific data (HDF5/Zarr) + + // Provenance tracking + char* provenance; + int provenance_size; +} NC_FILE_INFO_T; +``` + +**Location**: `include/nc4internal.h` + +### NC_GRP_INFO_T - Group Metadata + +```c +typedef struct NC_GRP_INFO_T { + NC_OBJ hdr; // Name and ID + struct NC_FILE_INFO_T* nc4_info; // Parent file + struct NC_GRP_INFO_T* parent; // Parent group (NULL for root) + + NCindex* children; // Child groups + NCindex* dim; // Dimensions + NCindex* att; // Attributes + NCindex* type; // User-defined types + NCindex* vars; // Variables + + void* format_grp_info; // Format-specific data +} NC_GRP_INFO_T; +``` + +**Key Feature**: Hierarchical group structure with NCindex for fast lookup + +### NC_VAR_INFO_T - Variable Metadata + +```c +typedef struct NC_VAR_INFO_T { + NC_OBJ hdr; // Name and ID + char* alt_name; // Alternate name (for format differences) + struct NC_GRP_INFO_T* container; // Parent group + + size_t ndims; // Number of dimensions + int* dimids; // Dimension IDs + NC_DIM_INFO_T** dim; // Dimension pointers + + nc_bool_t is_new_var; // Newly created + nc_bool_t was_coord_var; // Was a coordinate variable + nc_bool_t became_coord_var; // Became a coordinate variable + nc_bool_t fill_val_changed; // Fill value changed + nc_bool_t attr_dirty; // Attributes need rewriting + nc_bool_t created; // Already created in file + nc_bool_t written_to; // Has data been written + + struct NC_TYPE_INFO* type_info; // Type information + int atts_read; // Attributes read flag + nc_bool_t meta_read; // Metadata read flag + nc_bool_t coords_read; // Coordinates read flag + + NCindex* att; // Attributes + + nc_bool_t no_fill; // No fill value + void* fill_value; // Fill value + + size_t* chunksizes; // Chunk sizes (if chunked) + int storage; // NC_CHUNKED, NC_CONTIGUOUS, NC_COMPACT + int endianness; // NC_ENDIAN_NATIVE, NC_ENDIAN_LITTLE, NC_ENDIAN_BIG + int parallel_access; // NC_COLLECTIVE or NC_INDEPENDENT + + struct ChunkCache { + size_t size; // Cache size in bytes + size_t nelems; // Number of cache slots + float preemption; // Preemption policy + } chunkcache; + + int quantize_mode; // Quantization mode + int nsd; // Number of significant digits + + void* format_var_info; // Format-specific data + void* filters; // Filter list +} NC_VAR_INFO_T; +``` + +**Location**: `include/nc4internal.h:166-201` + +### NC_DIM_INFO_T - Dimension Metadata + +```c +typedef struct NC_DIM_INFO_T { + NC_OBJ hdr; // Name and ID + struct NC_GRP_INFO_T* container; // Parent group + size_t len; // Dimension length + nc_bool_t unlimited; // Is unlimited + nc_bool_t extended; // Needs extension + nc_bool_t too_long; // Length too large for size_t + void* format_dim_info; // Format-specific data + struct NC_VAR_INFO* coord_var; // Coordinate variable +} NC_DIM_INFO_T; +``` + +### NC_ATT_INFO_T - Attribute Metadata + +```c +typedef struct NC_ATT_INFO_T { + NC_OBJ hdr; // Name and ID + struct NC_OBJ* container; // Parent group or variable + size_t len; // Number of elements + nc_bool_t dirty; // Modified flag + nc_bool_t created; // Already created + nc_type nc_typeid; // Data type + void* format_att_info; // Format-specific data + void* data; // Attribute value +} NC_ATT_INFO_T; +``` + +### NC_TYPE_INFO_T - User-Defined Type + +```c +typedef struct NC_TYPE_INFO_T { + NC_OBJ hdr; // Name and ID + struct NC_GRP_INFO_T* container; // Parent group + unsigned rc; // Reference count + int endianness; // Byte order + size_t size; // Size in bytes + nc_bool_t committed; // Committed to file + nc_type nc_type_class; // NC_VLEN, NC_COMPOUND, NC_OPAQUE, NC_ENUM + void* format_type_info; // Format-specific data + int varsized; // Variable-sized flag + + union { + struct { + NClist* enum_member; // Enum members + nc_type base_nc_typeid; // Base type + } e; + + struct { + NClist* field; // Compound fields + } c; + + struct { + nc_type base_nc_typeid; // Base type + } v; + } u; +} NC_TYPE_INFO_T; +``` + +### NC_FIELD_INFO_T - Compound Field + +```c +typedef struct NC_FIELD_INFO_T { + NC_OBJ hdr; // Name and ID + nc_type nc_typeid; // Field type + size_t offset; // Offset in compound + int ndims; // Number of dimensions + int* dim_size; // Dimension sizes + void* format_field_info; // Format-specific data +} NC_FIELD_INFO_T; +``` + +### NC_OBJ - Common Object Header + +```c +typedef struct NC_OBJ { + NC_SORT sort; // NCVAR, NCDIM, NCATT, NCTYP, NCGRP, NCFIL + char* name; // Object name + int id; // Object ID +} NC_OBJ; +``` + +**Purpose**: Common header for all indexed objects. All structures that go into NCindex must start with NC_OBJ. + +## Index Structures + +### NCindex - Fast Lookup Index + +```c +typedef struct NCindex { + size_t count; // Number of entries + size_t alloc; // Allocated size + void** content; // Array of NC_OBJ* pointers +} NCindex; +``` + +**Location**: `include/ncindex.h` + +**Operations**: O(1) by ID, O(n) by name (uses linear search) + +### NC_hashmap - Hash Map + +```c +typedef struct NC_hashmap { + size_t size; // Hash table size + size_t count; // Number of entries + struct NC_hentry** table; // Hash table +} NC_hashmap; +``` + +**Location**: `include/nchashmap.h` + +**Operations**: O(1) average case for name lookup + +## I/O Structures + +### ncio - I/O Provider (NetCDF-3) + +```c +typedef struct ncio { + const char* path; // File path + int ioflags; // I/O flags + off_t offset; // Current offset + size_t extent; // File extent + size_t nciop_size; // Provider-specific size + + // Function pointers + int (*rel)(ncio*, off_t, int); + int (*get)(ncio*, off_t, size_t, int, void**); + int (*move)(ncio*, off_t, off_t, size_t); + int (*sync)(ncio*); + int (*filesize)(ncio*, off_t*); + int (*pad_length)(ncio*, off_t); + int (*close)(ncio*, int); + + void* pvt; // Private data +} ncio; +``` + +**Location**: `libsrc/ncio.h` + +**Implementations**: posixio, memio, httpio, s3io + +## Global State + +### NCglobalstate - Global Configuration + +```c +typedef struct NCglobalstate { + char* tempdir; // Temporary directory + char* home; // Home directory + char* cwd; // Current working directory + + struct NCRCinfo* rcinfo; // RC file info + + struct { + size_t size; // Chunk cache size + size_t nelems; // Number of elements + float preemption; // Preemption policy + } chunkcache; + + struct { + int threshold; // Alignment threshold + int alignment; // Alignment value + int defined; // Set flag + } alignment; + + struct { + char* default_region; // AWS region + char* config_file; // Config file path + char* profile; // Profile name + char* access_key_id; // Access key + char* secret_access_key; // Secret key + } aws; + + NClist* pluginpaths; // Filter plugin paths +} NCglobalstate; +``` + +**Location**: `libdispatch/ddispatch.c` + +**Access**: `NC_getglobalstate()` + +## Utility Structures + +### NC_string - Counted String + +```c +typedef struct { + size_t nchars; // String length + char* cp; // String data +} NC_string; +``` + +### NClist - Dynamic List + +```c +typedef struct NClist { + size_t alloc; // Allocated size + size_t length; // Current length + void** content; // Array of pointers +} NClist; +``` + +### NCbytes - Dynamic Byte Buffer + +```c +typedef struct NCbytes { + size_t alloc; // Allocated size + size_t length; // Current length + char* content; // Buffer +} NCbytes; +``` + +## Format-Specific Structures + +### HDF5-Specific (NC_HDF5_FILE_INFO_T, etc.) + +Stored in `format_file_info`, `format_var_info`, etc. fields. + +Contains HDF5 handles (hid_t), property lists, and other HDF5-specific data. + +### Zarr-Specific (NCZ_FILE_INFO_T, etc.) + +Stored in `format_file_info`, `format_var_info`, etc. fields. + +Contains Zarr metadata (JSON), chunk cache, zmap handles, codec information. + +## Memory Management + +**Allocation**: Most structures use `calloc()` for zero-initialization + +**Deallocation**: Each structure type has a corresponding `free_*()` function + +**Reference Counting**: User-defined types use reference counting (`NC_TYPE_INFO_T.rc`) + +**String Handling**: NC_string structures manage their own memory + +## Thread Safety + +**Global State**: Protected by internal locks (implementation-dependent) + +**File Handles**: Not thread-safe - one thread per file handle + +**Parallel I/O**: Uses MPI for coordination, not threading diff --git a/windsurf-harnett/skills/netcdf-architecture/references/DISPATCH-TABLES.md b/windsurf-harnett/skills/netcdf-architecture/references/DISPATCH-TABLES.md new file mode 100644 index 000000000..787364968 --- /dev/null +++ b/windsurf-harnett/skills/netcdf-architecture/references/DISPATCH-TABLES.md @@ -0,0 +1,707 @@ +# NetCDF-C Dispatch Tables Reference + +This reference documents all dispatch table implementations in NetCDF-C. + +## Dispatch Table Overview + +The `NC_Dispatch` structure contains function pointers for all NetCDF operations. Each format implements this interface. + +**Definition**: `include/netcdf_dispatch.h:34-256` + +**Current Version**: `NC_DISPATCH_VERSION = 5` + +## NC3 Dispatch Table (NetCDF-3) + +**File**: `libsrc/nc3dispatch.c:81-174` + +**Table Name**: `NC3_dispatcher` + +**Model**: `NC_FORMATX_NC3` + +### Implementation Summary + +**Fully Implemented**: +- File operations: create, open, close, sync, abort, redef, enddef +- Dimensions: def_dim, inq_dim, inq_dimid, inq_unlimdim, rename_dim +- Variables: def_var, inq_var, inq_varid, rename_var +- Attributes: put_att, get_att, inq_att, del_att, rename_att +- Variable I/O: get_vara, put_vara +- Inquiry: inq, inq_format, inq_type +- Fill values: set_fill, def_var_fill + +**Delegated to NCDEFAULT**: +- get_vars, put_vars (strided access) +- get_varm, put_varm (mapped access) + +**Returns NC_ENOTNC4** (not supported): +- Groups: def_grp, rename_grp, inq_grps, inq_grp_parent +- User-defined types: def_compound, def_vlen, def_enum, def_opaque +- Compression: def_var_deflate, def_var_fletcher32 +- Chunking: def_var_chunking, set_var_chunk_cache +- Filters: def_var_filter, inq_var_filter_ids +- Endianness: def_var_endian +- Quantization: def_var_quantize + +**Special Implementations**: +- `inq_unlimdims`: Returns single unlimited dimension (NC3 has max 1) +- `inq_ncid`: Returns same ncid (no groups) +- `inq_grpname`: Returns "/" (root only) +- `inq_varids`, `inq_dimids`: Returns sequential IDs 0..n-1 + +### Key Functions + +```c +static const NC_Dispatch NC3_dispatcher = { + .model = NC_FORMATX_NC3, + .dispatch_version = NC_DISPATCH_VERSION, + + .create = NC3_create, + .open = NC3_open, + .redef = NC3_redef, + ._enddef = NC3__enddef, + .sync = NC3_sync, + .abort = NC3_abort, + .close = NC3_close, + + .get_vara = NC3_get_vara, + .put_vara = NC3_put_vara, + .get_vars = NCDEFAULT_get_vars, + .put_vars = NCDEFAULT_put_vars, + + // ... more function pointers +}; +``` + +## HDF5 Dispatch Table (NetCDF-4/HDF5) + +**File**: `libhdf5/hdf5dispatch.c:19-114` + +**Table Name**: `HDF5_dispatcher` + +**Model**: `NC_FORMATX_NC4` + +### Implementation Summary + +**Fully Implemented**: +- All file operations +- All dimension operations (with HDF5 dimension scales) +- All variable operations (with chunking, compression, filters) +- All attribute operations (including reserved attributes) +- All group operations (hierarchical groups) +- All user-defined types (compound, vlen, enum, opaque) +- Compression and filters +- Chunking and endianness +- Parallel I/O (if HDF5 built with parallel support) +- Quantization (NetCDF-4.8+) + +**Delegated to NCDEFAULT**: +- get_varm, put_varm (mapped access) + +**HDF5-Specific Features**: +- Dimension scales for dimensions +- Reserved attributes (_NCProperties, _Netcdf4Coordinates, etc.) +- Filter plugins +- Chunk cache tuning +- Parallel I/O via MPI + +### Key Functions + +```c +static const NC_Dispatch HDF5_dispatcher = { + .model = NC_FORMATX_NC4, + .dispatch_version = NC_DISPATCH_VERSION, + + .create = NC4_create, + .open = NC4_open, + + .def_dim = HDF5_def_dim, + .inq_dim = HDF5_inq_dim, + .rename_dim = HDF5_rename_dim, + + .def_var = NC4_def_var, + .get_vara = NC4_get_vara, + .put_vara = NC4_put_vara, + .get_vars = NC4_get_vars, + .put_vars = NC4_put_vars, + + .def_var_deflate = NC4_def_var_deflate, + .def_var_chunking = NC4_def_var_chunking, + .def_var_filter = NC4_hdf5_def_var_filter, + + .def_grp = NC4_def_grp, + .def_compound = NC4_def_compound, + + // ... more function pointers +}; +``` + +## Zarr Dispatch Table (NCZarr) + +**File**: `libnczarr/zdispatch.c:19-111` + +**Table Name**: `NCZ_dispatcher` + +**Model**: `NC_FORMATX_NCZARR` + +### Implementation Summary + +**Fully Implemented**: +- File operations (create, open, close, sync) +- Variable I/O (get_vara, put_vara, get_vars, put_vars) +- Zarr-specific metadata operations +- Codec/filter pipeline +- Chunk caching + +**Delegated to NC4 (libsrc4)**: +- Most inquiry operations (inq_type, inq_dimid, inq_varid, etc.) +- Group operations (inq_grps, inq_grpname, etc.) +- Many metadata operations + +**Returns NC_NOTNC4** (not supported): +- User-defined types (compound, vlen, enum, opaque) +- Some type operations + +**Zarr-Specific Features**: +- JSON metadata (.zarray, .zgroup, .zattrs) +- Multiple storage backends (file, S3, ZIP) +- Codec pipeline (blosc, zlib, etc.) +- Dimension separator (. or /) + +### Key Functions + +```c +static const NC_Dispatch NCZ_dispatcher = { + .model = NC_FORMATX_NCZARR, + .dispatch_version = NC_DISPATCH_VERSION, + + .create = NCZ_create, + .open = NCZ_open, + .close = NCZ_close, + .sync = NCZ_sync, + + .get_vara = NCZ_get_vara, + .put_vara = NCZ_put_vara, + .get_vars = NCZ_get_vars, + .put_vars = NCZ_put_vars, + + // Many operations delegate to NC4_* + .inq_type = NCZ_inq_type, // Calls NC4_inq_type + .inq_dimid = NCZ_inq_dimid, // Calls NC4_inq_dimid + + .def_var_filter = NCZ_def_var_filter, + .def_var_chunking = NCZ_def_var_chunking, + + // User-defined types not supported + .def_compound = NC_NOTNC4_def_compound, + .def_vlen = NC_NOTNC4_def_vlen, + + // ... more function pointers +}; +``` + +## DAP2 Dispatch Table (OPeNDAP) + +**File**: `libdap2/ncd2dispatch.c` + +**Table Name**: `NCD2_dispatcher` + +**Model**: `NC_FORMATX_DAP2` + +### Implementation Summary + +**Fully Implemented**: +- File operations (open, close) +- Variable I/O with constraint expressions +- Metadata inquiry +- Attribute access +- Remote data access via HTTP + +**Not Supported** (read-only protocol): +- create, redef, enddef +- def_dim, def_var, put_att +- put_vara, put_vars +- All NetCDF-4 features + +**DAP2-Specific Features**: +- Constraint expressions for subsetting +- DDS/DAS parsing +- HTTP caching +- URL-based access + +### Key Functions + +```c +static const NC_Dispatch NCD2_dispatcher = { + .model = NC_FORMATX_DAP2, + .dispatch_version = NC_DISPATCH_VERSION, + + .create = NULL, // Not supported + .open = NCD2_open, + .close = NCD2_close, + + .get_vara = NCD2_get_vara, + .put_vara = NULL, // Read-only + + .inq = NCD2_inq, + .inq_var = NCD2_inq_var, + .get_att = NCD2_get_att, + + // ... more function pointers +}; +``` + +## DAP4 Dispatch Table (OPeNDAP) + +**File**: `libdap4/ncd4dispatch.c` + +**Table Name**: `NCD4_dispatcher` + +**Model**: `NC_FORMATX_DAP4` + +### Implementation Summary + +**Fully Implemented**: +- File operations (open, close) +- Variable I/O +- Metadata inquiry (DMR parsing) +- Group support +- Enhanced type system + +**Not Supported** (read-only protocol): +- create, redef, enddef +- def_dim, def_var, put_att +- put_vara, put_vars +- User-defined types (read-only) + +**DAP4-Specific Features**: +- DMR (XML metadata) +- Groups and hierarchies +- Checksums +- Chunked transfer encoding + +### Key Functions + +```c +static const NC_Dispatch NCD4_dispatcher = { + .model = NC_FORMATX_DAP4, + .dispatch_version = NC_DISPATCH_VERSION, + + .create = NULL, // Not supported + .open = NCD4_open, + .close = NCD4_close, + + .get_vara = NCD4_get_vara, + .put_vara = NULL, // Read-only + + .inq_grps = NCD4_inq_grps, // Groups supported + + // ... more function pointers +}; +``` + +## User-Defined Format Tables + +**Files**: `libdispatch/dfile.c`, `libdispatch/ddispatch.c` + +**Table Names**: `UDF0_dispatch_table` through `UDF9_dispatch_table` + +**Models**: `NC_FORMATX_UDF0` through `NC_FORMATX_UDF9` + +**Mode Flags**: `NC_UDF0` through `NC_UDF9` + +### Overview + +NetCDF-C provides 10 user-defined format slots that allow developers to extend the library with custom file formats and storage backends. Each slot can be independently configured with its own dispatch table, initialization function, and optional magic number. + +### UDF Slot Organization + +- **UDF0, UDF1**: Original slots, mode flags in lower 16 bits +- **UDF2-UDF9**: Extended slots, mode flags in upper 16 bits + +### Registration Methods + +#### Programmatic Registration + +Users can register custom formats via `nc_def_user_format()`: + +```c +int nc_def_user_format(int mode_flag, + NC_Dispatch* dispatch_table, + char* magic_number); +``` + +**Parameters**: +- `mode_flag`: One of `NC_UDF0` through `NC_UDF9`, optionally combined with other mode flags (e.g., `NC_NETCDF4`) +- `dispatch_table`: Pointer to your `NC_Dispatch` structure +- `magic_number`: Optional magic number string (max `NC_MAX_MAGIC_NUMBER_LEN` bytes) for automatic format detection, or NULL + +**Example**: +```c +extern NC_Dispatch my_format_dispatcher; + +// Register UDF in slot 0 with magic number +nc_def_user_format(NC_UDF0 | NC_NETCDF4, &my_format_dispatcher, "MYFORMAT"); + +// Now files with "MYFORMAT" magic number will use your dispatcher +int ncid; +nc_open("myfile.dat", 0, &ncid); // Auto-detects format +``` + +#### Query Registered UDFs + +Use `nc_inq_user_format()` to query registered formats: + +```c +int nc_inq_user_format(int mode_flag, + NC_Dispatch** dispatch_table, + char* magic_number); +``` + +**Example**: +```c +NC_Dispatch *disp; +char magic[NC_MAX_MAGIC_NUMBER_LEN + 1]; +nc_inq_user_format(NC_UDF0, &disp, magic); +``` + +#### RC File Configuration + +UDFs can be automatically loaded from RC file configuration: + +**RC File Format** (`.ncrc`, `.daprc`, or `.dodsrc`): +```ini +NETCDF.UDF.LIBRARY=/full/path/to/library.so +NETCDF.UDF.INIT=initialization_function_name +NETCDF.UDF.MAGIC=OPTIONAL_MAGIC_NUMBER +``` + +**Example**: +```ini +# Load custom format in UDF0 +NETCDF.UDF0.LIBRARY=/usr/local/lib/libmyformat.so +NETCDF.UDF0.INIT=myformat_init +NETCDF.UDF0.MAGIC=MYFORMAT + +# Load scientific data format in UDF3 +NETCDF.UDF3.LIBRARY=/opt/scidata/lib/libscidata.so +NETCDF.UDF3.INIT=scidata_initialize +NETCDF.UDF3.MAGIC=SCIDATA +``` + +**RC File Requirements**: +- `LIBRARY`: Must be a full absolute path to the shared library +- `INIT`: Name of the initialization function in the library +- `MAGIC`: Optional magic number for automatic format detection +- Both `LIBRARY` and `INIT` must be present; partial configuration is ignored with a warning + +**RC File Search Order**: +1. `$HOME/.ncrc` +2. `$HOME/.daprc` +3. `$HOME/.dodsrc` +4. `$CWD/.ncrc` +5. `$CWD/.daprc` +6. `$CWD/.dodsrc` + +### Plugin Loading Process + +Plugins are loaded during library initialization (`nc_initialize()`): + +1. RC files are parsed +2. For each configured UDF slot: + - Library is loaded using `dlopen()` (Unix) or `LoadLibrary()` (Windows) + - Init function is located using `dlsym()` or `GetProcAddress()` + - Init function is called + - Init function must call `nc_def_user_format()` to register the dispatch table +3. Dispatch table ABI version is verified +4. Magic number (if provided) is registered for automatic format detection + +**Note**: Library handles are intentionally not closed; they remain loaded for the lifetime of the process. + +### Plugin Implementation Requirements + +**Dispatch Table Requirements**: +- Dispatch table version must match `NC_DISPATCH_VERSION` +- Must implement all required operations or use pre-defined stubs +- Magic number max `NC_MAX_MAGIC_NUMBER_LEN` bytes (optional) + +**Initialization Function Requirements**: +1. Must be exported (not static) +2. Must call `nc_def_user_format()` to register dispatch table +3. Should return `NC_NOERR` on success, error code on failure +4. Name must match RC file `INIT` key + +**Example Initialization Function**: +```c +#include + +extern NC_Dispatch my_dispatcher; + +// Initialization function - must be exported +int my_plugin_init(void) { + int ret; + + // Register dispatch table with magic number + ret = nc_def_user_format(NC_UDF0 | NC_NETCDF4, + &my_dispatcher, + "MYFMT"); + if (ret != NC_NOERR) + return ret; + + // Additional initialization if needed + // ... + + return NC_NOERR; +} +``` + +### Pre-defined Dispatch Functions + +For operations your format doesn't support, use these pre-defined functions: + +**Read-only stubs** (`libdispatch/dreadonly.c`): +- `NC_RO_create`, `NC_RO_redef`, `NC_RO__enddef`, `NC_RO_sync` +- `NC_RO_set_fill`, `NC_RO_def_dim`, `NC_RO_def_var`, `NC_RO_put_att` +- `NC_RO_put_vara`, `NC_RO_put_vars`, `NC_RO_put_varm` +- Returns `NC_EPERM` (operation not permitted) + +**Not NetCDF-4 stubs** (`libdispatch/dnotnc4.c`): +- `NC_NOTNC4_def_grp`, `NC_NOTNC4_def_compound`, `NC_NOTNC4_def_vlen` +- `NC_NOTNC4_def_var_deflate`, `NC_NOTNC4_def_var_chunking` +- Returns `NC_ENOTNC4` (not a NetCDF-4 file) + +**Not NetCDF-3 stubs** (`libdispatch/dnotnc3.c`): +- Returns `NC_ENOTNC3` (not a NetCDF-3 file) + +**No-op stubs**: +- `NC_NOOP_*` - Returns `NC_NOERR` without doing anything + +**Default implementations** (`libdispatch/dvar.c`): +- `NCDEFAULT_get_vars`, `NCDEFAULT_put_vars` - Strided access using get_vara/put_vara +- `NCDEFAULT_get_varm`, `NCDEFAULT_put_varm` - Mapped access using get_vars/put_vars + +**NetCDF-4 inquiry functions** (`libsrc4/`): +- `NC4_inq`, `NC4_inq_type`, `NC4_inq_dimid`, `NC4_inq_varid` +- Use internal metadata model for inquiry operations + +### Example Minimal Dispatch Table + +```c +#include "netcdf_dispatch.h" + +static NC_Dispatch my_dispatcher = { + NC_FORMATX_UDF0, /* Use UDF slot 0 */ + NC_DISPATCH_VERSION, /* Current ABI version */ + + NC_RO_create, /* Read-only: use predefined function */ + my_open, /* Custom open function */ + NC_RO_redef, + NC_RO__enddef, + NC_RO_sync, + my_abort, + my_close, + NC_RO_set_fill, + my_inq_format, + my_inq_format_extended, + + /* Inquiry functions - can use NC4_* defaults */ + NC4_inq, + NC4_inq_type, + NC4_inq_dimid, + NC4_inq_varid, + + /* Variable I/O */ + my_get_vara, + NC_RO_put_vara, /* Read-only */ + NCDEFAULT_get_vars, /* Use default strided implementation */ + NC_RO_put_vars, + NCDEFAULT_get_varm, /* Use default mapped implementation */ + NC_RO_put_varm, + + /* NetCDF-4 features not supported */ + NC_NOTNC4_def_grp, + NC_NOTNC4_def_compound, + NC_NOTNC4_def_vlen, + NC_NOTNC4_def_var_deflate, + NC_NOTNC4_def_var_chunking, + + /* ... continue for all ~70 function pointers ... */ +}; +``` + +### Magic Numbers and Format Detection + +Magic numbers enable automatic format detection when opening files. + +**How Magic Numbers Work**: +1. When `nc_open()` is called without a specific format flag +2. The file's first bytes are read +3. They are compared against all registered magic numbers (built-in and user-defined) +4. If a match is found, the corresponding dispatcher is used + +**Magic Number Best Practices**: +- Use unique, distinctive strings (4-8 bytes recommended) +- Place at the beginning of your file format +- Avoid conflicts with existing formats: + - NetCDF-3: "CDF\001", "CDF\002", "CDF\005" + - HDF5/NetCDF-4: "\211HDF\r\n\032\n" +- Maximum length: `NC_MAX_MAGIC_NUMBER_LEN` bytes + +### Platform Considerations + +**Unix/Linux/macOS**: +- Shared libraries: `.so` extension +- Dynamic loading: `dlopen()` and `dlsym()` +- Library paths: Use absolute paths or ensure libraries are in `LD_LIBRARY_PATH` + +**Windows**: +- Shared libraries: `.dll` extension +- Dynamic loading: `LoadLibrary()` and `GetProcAddress()` +- Library paths: Use absolute paths or ensure DLLs are in system `PATH` + +**Building Plugins**: + +Unix: +```bash +gcc -shared -fPIC -o libmyplugin.so myplugin.c -lnetcdf +``` + +Windows: +```batch +cl /LD myplugin.c netcdf.lib +``` + +### Security Considerations + +- **Full paths required**: RC files must specify absolute library paths to prevent path injection attacks +- **Code execution**: Plugins execute arbitrary code in your process; only load trusted libraries +- **Validation**: The library verifies dispatch table ABI version but cannot validate plugin behavior +- **Permissions**: Ensure plugin libraries have appropriate file permissions + +### Common Errors + +**NC_EINVAL: Invalid dispatch table version** +- Cause: Plugin was compiled against a different version of NetCDF-C +- Solution: Recompile plugin against current NetCDF-C version + +**Plugin not loaded (no error)** +- Cause: Partial RC configuration (LIBRARY without INIT, or vice versa) +- Solution: Check that both LIBRARY and INIT keys are present in RC file + +**Library not found** +- Cause: Incorrect path in NETCDF.UDF*.LIBRARY +- Solution: Use absolute path; verify file exists and has correct permissions + +**Init function not found** +- Cause: Function name mismatch or missing export +- Solution: Verify function name matches INIT key; ensure function is exported (not static) + +### Testing UDFs + +**Enable Logging**: +```bash +export NC_LOG_LEVEL=3 +./myprogram +``` + +**Verify RC File is Read**: +```bash +echo "NETCDF.UDF0.LIBRARY=/tmp/test.so" > ~/.ncrc +echo "NETCDF.UDF0.INIT=test_init" >> ~/.ncrc +# Run program and check for warnings about missing library +``` + +**Check Plugin Exports** (Unix): +```bash +nm -D libmyplugin.so | grep init +``` + +**Check Plugin Exports** (Windows): +```batch +dumpbin /EXPORTS myplugin.dll +``` + +## Dispatch Table Selection + +**File**: `libdispatch/dinfermodel.c` + +### Selection Logic + +1. **Magic Number Detection**: + - CDF1: `0x43 0x44 0x46 0x01` ("CDF\001") + - CDF2: `0x43 0x44 0x46 0x02` ("CDF\002") + - CDF5: `0x43 0x44 0x46 0x05` ("CDF\005") + - HDF5: `0x89 0x48 0x44 0x46 0x0d 0x0a 0x1a 0x0a` + - User-defined: Custom magic numbers + +2. **URL Scheme Parsing**: + - `http://`, `https://` → DAP2 or DAP4 + - `s3://` → Zarr with S3 backend + - `file://` → Local file (check magic) + +3. **Mode Flags**: + - `NC_NETCDF4` → HDF5 or Zarr + - `NC_CLASSIC_MODEL` → NetCDF-3 API with NetCDF-4 file + - `NC_64BIT_OFFSET` → CDF2 + - `NC_64BIT_DATA` → CDF5 + - `NC_ZARR` → Zarr format + +4. **File Extension** (hints): + - `.nc` → NetCDF-3 or NetCDF-4 + - `.nc4` → NetCDF-4/HDF5 + - `.h5`, `.hdf5` → HDF5 + - `.zarr` → Zarr + +### Dispatch Table Registration + +**Initialization** (called at library startup): +```c +NCDISPATCH_initialize() + → NC3_initialize() // Sets NC3_dispatch_table + → NC_HDF5_initialize() // Sets HDF5_dispatch_table + → NCZ_initialize() // Sets NCZ_dispatch_table + → NCD2_initialize() // Sets NCD2_dispatch_table + → NCD4_initialize() // Sets NCD4_dispatch_table +``` + +## Function Pointer Conventions + +### Return Values +- `NC_NOERR` (0) on success +- Negative error codes on failure +- `NC_ENOTNC4` for unsupported NetCDF-4 features +- `NC_EINVAL` for invalid parameters + +### Common Stubs + +**NC_NOOP_*** - No-operation stubs (return NC_NOERR) +**NC_NOTNC4_*** - Not-NetCDF-4 stubs (return NC_ENOTNC4) +**NCDEFAULT_*** - Default implementations (in libdispatch) + +### NCDEFAULT Implementations + +**File**: `libdispatch/dvar.c` + +- `NCDEFAULT_get_vars()` - Implements strided access using get_vara +- `NCDEFAULT_put_vars()` - Implements strided writes using put_vara +- `NCDEFAULT_get_varm()` - Implements mapped access using get_vars +- `NCDEFAULT_put_varm()` - Implements mapped writes using put_vars + +## Dispatch Version History + +- **Version 1**: Original dispatch table +- **Version 2**: Added filter operations +- **Version 3**: Replaced filteractions with specific filter functions +- **Version 4**: Added quantization support +- **Version 5**: Current version (additional enhancements) + +**Compatibility**: Dispatch tables must match the library's dispatch version exactly. + +## Testing Dispatch Tables + +Each format has its own test suite: +- `nc_test/` - NetCDF-3 tests +- `nc_test4/` - NetCDF-4/HDF5 tests +- `nczarr_test/` - Zarr tests +- `ncdap_test/` - DAP2 tests + +**Dispatch testing**: Tests verify that operations route correctly and return appropriate errors for unsupported features. diff --git a/windsurf-harnett/skills/netcdf-architecture/references/EXAMPLES.md b/windsurf-harnett/skills/netcdf-architecture/references/EXAMPLES.md new file mode 100644 index 000000000..379377c05 --- /dev/null +++ b/windsurf-harnett/skills/netcdf-architecture/references/EXAMPLES.md @@ -0,0 +1,463 @@ +# NetCDF-C Programming Examples and Patterns + +This reference provides practical examples and common programming patterns for working with NetCDF-C, based on official documentation and example programs. + +## Overview + +NetCDF-C provides example programs demonstrating: +- Basic file creation and reading (NetCDF-3) +- Enhanced features (NetCDF-4: groups, compression, user-defined types) +- Real-world patterns (meteorological data, time series, multidimensional arrays) + +**Example Location**: `examples/C/` directory in NetCDF-C source + +**Documentation**: https://docs.unidata.ucar.edu/netcdf-c/current/examples1.html + +## Basic NetCDF-3 Examples + +### Example 1: simple_xy - Minimal File Operations + +**Files**: `simple_xy_wr.c`, `simple_xy_rd.c` + +**Purpose**: Demonstrates absolute minimum operations to create and read a NetCDF file. + +**What it creates**: 2D array (6x12) with dimensions "x" and "y", variable "data" + +**Key Pattern - File Creation**: +```c +int ncid, x_dimid, y_dimid, varid; +int dimids[2]; + +// Create file (NC_CLOBBER overwrites existing) +nc_create("simple_xy.nc", NC_CLOBBER, &ncid); + +// Define dimensions +nc_def_dim(ncid, "x", NX, &x_dimid); +nc_def_dim(ncid, "y", NY, &y_dimid); + +// Define variable with dimensions +dimids[0] = x_dimid; +dimids[1] = y_dimid; +nc_def_var(ncid, "data", NC_INT, 2, dimids, &varid); + +// End define mode (required before writing data) +nc_enddef(ncid); + +// Write data +nc_put_var_int(ncid, varid, &data[0][0]); + +// Close file +nc_close(ncid); +``` + +**Key Pattern - File Reading**: +```c +int ncid, varid; +int data_in[NX][NY]; + +// Open file for reading +nc_open("simple_xy.nc", NC_NOWRITE, &ncid); + +// Get variable ID by name +nc_inq_varid(ncid, "data", &varid); + +// Read entire variable +nc_get_var_int(ncid, varid, &data_in[0][0]); + +// Close file +nc_close(ncid); +``` + +### Example 2: sfc_pres_temp - Adding Metadata + +**Files**: `sfc_pres_temp_wr.c`, `sfc_pres_temp_rd.c` + +**Purpose**: Demonstrates adding attributes and coordinate variables (CF conventions). + +**What it creates**: Surface temperature and pressure on 6x12 lat/lon grid with metadata + +**Key Pattern - Adding Attributes**: +```c +// Define variable +nc_def_var(ncid, "temperature", NC_FLOAT, 2, dimids, &temp_varid); + +// Add units attribute +nc_put_att_text(ncid, temp_varid, "units", 11, "celsius"); + +// Add global attribute +nc_put_att_text(ncid, NC_GLOBAL, "title", 23, "Surface Temperature Data"); +``` + +**Key Pattern - Coordinate Variables**: +```c +// Define latitude dimension +nc_def_dim(ncid, "latitude", NLAT, &lat_dimid); + +// Define latitude coordinate variable (same name as dimension) +nc_def_var(ncid, "latitude", NC_FLOAT, 1, &lat_dimid, &lat_varid); +nc_put_att_text(ncid, lat_varid, "units", 13, "degrees_north"); + +// Write coordinate values +float lats[NLAT] = {25, 30, 35, 40, 45, 50}; +nc_put_var_float(ncid, lat_varid, lats); +``` + +**Best Practice**: Coordinate variables should have the same name as their dimension and include units. + +### Example 3: pres_temp_4D - Unlimited Dimensions and Time Series + +**Files**: `pres_temp_4D_wr.c`, `pres_temp_4D_rd.c` + +**Purpose**: Demonstrates 4D data with unlimited time dimension, writing one timestep at a time. + +**What it creates**: Temperature and pressure with dimensions [time, level, lat, lon] + +**Key Pattern - Unlimited Dimension**: +```c +// Define unlimited dimension (use NC_UNLIMITED for size) +nc_def_dim(ncid, "time", NC_UNLIMITED, &time_dimid); + +// Define variable with unlimited dimension first +int dimids[4] = {time_dimid, level_dimid, lat_dimid, lon_dimid}; +nc_def_var(ncid, "temperature", NC_FLOAT, 4, dimids, &temp_varid); +``` + +**Key Pattern - Writing Time Steps**: +```c +// Write one time step at a time +for (int rec = 0; rec < NREC; rec++) { + size_t start[4] = {rec, 0, 0, 0}; // Start at this time step + size_t count[4] = {1, NLVL, NLAT, NLON}; // Write one time slice + + // Prepare data for this time step + float temp_out[NLVL][NLAT][NLON]; + // ... fill temp_out ... + + // Write hyperslab + nc_put_vara_float(ncid, temp_varid, start, count, &temp_out[0][0][0]); +} +``` + +**Key Pattern - Reading Time Steps**: +```c +// Read one time step +size_t start[4] = {rec, 0, 0, 0}; +size_t count[4] = {1, NLVL, NLAT, NLON}; +nc_get_vara_float(ncid, temp_varid, start, count, &temp_in[0][0][0]); +``` + +## NetCDF-4 Enhanced Examples + +### Example 4: simple_nc4 - Groups and User-Defined Types + +**Files**: `simple_nc4_wr.c`, `simple_nc4_rd.c` + +**Purpose**: Demonstrates NetCDF-4 groups and compound types. + +**What it creates**: Two groups with different data types (uint64 and compound) + +**Key Pattern - Creating Groups**: +```c +int ncid, grp1_id, grp2_id; + +// Create NetCDF-4 file +nc_create("simple_nc4.nc", NC_NETCDF4, &ncid); + +// Create groups +nc_def_grp(ncid, "grp1", &grp1_id); +nc_def_grp(ncid, "grp2", &grp2_id); + +// Define variable in group +nc_def_var(grp1_id, "data", NC_UINT64, 2, dimids, &varid); +``` + +**Key Pattern - Compound Types**: +```c +typedef struct { + int i1; + int i2; +} compound_data; + +nc_type compound_typeid; + +// Define compound type +nc_def_compound(grp2_id, sizeof(compound_data), "compound_t", &compound_typeid); +nc_insert_compound(grp2_id, compound_typeid, "i1", + NC_COMPOUND_OFFSET(compound_data, i1), NC_INT); +nc_insert_compound(grp2_id, compound_typeid, "i2", + NC_COMPOUND_OFFSET(compound_data, i2), NC_INT); + +// Use compound type for variable +nc_def_var(grp2_id, "data", compound_typeid, 2, dimids, &varid); +``` + +### Example 5: simple_xy_nc4 - Compression and Chunking + +**Files**: `simple_xy_nc4_wr.c`, `simple_xy_nc4_rd.c` + +**Purpose**: Demonstrates chunking, compression, and checksums (HDF5 features). + +**Key Pattern - Chunking**: +```c +// Define variable +nc_def_var(ncid, "data", NC_INT, 2, dimids, &varid); + +// Set chunking (required for compression) +size_t chunks[2] = {4, 4}; // Chunk size for each dimension +nc_def_var_chunking(ncid, varid, NC_CHUNKED, chunks); +``` + +**Key Pattern - Compression**: +```c +// Enable deflate compression (level 1-9, 9 = maximum compression) +int shuffle = NC_SHUFFLE; // Shuffle filter improves compression +int deflate = 1; // Enable deflate +int deflate_level = 5; // Compression level +nc_def_var_deflate(ncid, varid, shuffle, deflate, deflate_level); +``` + +**Key Pattern - Checksums**: +```c +// Enable fletcher32 checksum for data integrity +nc_def_var_fletcher32(ncid, varid, NC_FLETCHER32); +``` + +### Example 6: filter_example - Custom Filters + +**Files**: `filter_example.c` + +**Purpose**: Demonstrates using custom compression filters (e.g., bzip2). + +**Key Pattern - Custom Filter**: +```c +// Define variable with chunking (required for filters) +nc_def_var(ncid, "data", NC_INT, 2, dimids, &varid); +size_t chunks[2] = {100, 100}; +nc_def_var_chunking(ncid, varid, NC_CHUNKED, chunks); + +// Apply custom filter (bzip2 example) +unsigned int filter_id = 307; // Bzip2 filter ID +size_t nparams = 1; +unsigned int params[1] = {9}; // Compression level +nc_def_var_filter(ncid, varid, filter_id, nparams, params); +``` + +## Common Programming Patterns + +### Pattern 1: Error Handling + +**Always check return codes**: +```c +int retval; + +if ((retval = nc_create(FILE_NAME, NC_CLOBBER, &ncid))) + ERR(retval); + +// Or use macro +#define ERR(e) {printf("Error: %s\n", nc_strerror(e)); return 2;} +``` + +### Pattern 2: Inquiry Functions + +**Get file information without prior knowledge**: +```c +int ncid, ndims, nvars, ngatts, unlimdimid; + +// Open file +nc_open("file.nc", NC_NOWRITE, &ncid); + +// Get file metadata +nc_inq(ncid, &ndims, &nvars, &ngatts, &unlimdimid); + +// Inquire about specific dimension +char dim_name[NC_MAX_NAME+1]; +size_t dim_len; +nc_inq_dim(ncid, dimid, dim_name, &dim_len); + +// Inquire about variable +char var_name[NC_MAX_NAME+1]; +nc_type var_type; +int var_ndims, var_dimids[NC_MAX_VAR_DIMS], var_natts; +nc_inq_var(ncid, varid, var_name, &var_type, &var_ndims, + var_dimids, &var_natts); +``` + +### Pattern 3: Subsetting Data (Hyperslabs) + +**Read/write portions of arrays**: +```c +// Read a subset: time=5, all levels, lat 10-20, lon 30-40 +size_t start[4] = {5, 0, 10, 30}; +size_t count[4] = {1, NLVL, 10, 10}; +float subset[NLVL][10][10]; + +nc_get_vara_float(ncid, varid, start, count, &subset[0][0][0]); +``` + +### Pattern 4: Strided Access + +**Read every Nth element**: +```c +// Read every 2nd element in each dimension +size_t start[2] = {0, 0}; +size_t count[2] = {NX/2, NY/2}; +ptrdiff_t stride[2] = {2, 2}; // Skip every other element + +nc_get_vars_float(ncid, varid, start, count, stride, data); +``` + +### Pattern 5: Fill Values + +**Handle missing data**: +```c +// Set custom fill value +float fill_value = -999.0; +nc_def_var_fill(ncid, varid, NC_FILL, &fill_value); + +// Disable fill values (for performance) +nc_def_var_fill(ncid, varid, NC_NOFILL, NULL); +``` + +### Pattern 6: Parallel I/O (NetCDF-4 with HDF5) + +**MPI parallel access**: +```c +#include + +// Initialize MPI +MPI_Init(&argc, &argv); +MPI_Comm_size(MPI_COMM_WORLD, &nprocs); +MPI_Comm_rank(MPI_COMM_WORLD, &rank); + +// Create file with parallel access +nc_create_par("parallel.nc", NC_NETCDF4|NC_MPIIO, + MPI_COMM_WORLD, MPI_INFO_NULL, &ncid); + +// Set collective access for variable +nc_var_par_access(ncid, varid, NC_COLLECTIVE); + +// Each process writes its portion +size_t start[1] = {rank * chunk_size}; +size_t count[1] = {chunk_size}; +nc_put_vara_float(ncid, varid, start, count, local_data); + +// Close and finalize +nc_close(ncid); +MPI_Finalize(); +``` + +## Best Practices from Examples + +### File Creation +1. **Always use NC_CLOBBER or NC_NOCLOBBER** to control overwrite behavior +2. **Classic CDF-1 is the default**: `nc_create(path, NC_CLOBBER, &ncid)` creates a classic file — no format flag needed +3. **NC_CLASSIC_MODEL is only for NetCDF-4**: Use `NC_NETCDF4 | NC_CLASSIC_MODEL` to get HDF5 storage with classic data model restrictions (no groups, no user-defined types). Do NOT use `NC_CLASSIC_MODEL` alone for classic CDF-1 files. +4. **End define mode** with `nc_enddef()` before writing data +5. **Close files** with `nc_close()` to ensure data is flushed + +### Dimensions +1. **Unlimited dimension first** in dimension order for best performance +2. **One unlimited dimension** in NetCDF-3, multiple allowed in NetCDF-4 +3. **Coordinate variables** should match dimension names + +### Variables +1. **Add units attribute** to all data variables (CF convention) +2. **Use appropriate data types** (NC_FLOAT for most scientific data) +3. **Enable chunking** before compression or filters + +### Attributes +1. **Use standard names** from CF conventions when possible +2. **Add global attributes** for file-level metadata (title, history, etc.) +3. **Document missing values** with _FillValue or missing_value attributes + +### Performance +1. **Use chunking** for large datasets accessed in subsets +2. **Enable compression** to reduce file size (deflate level 5 is good default) +3. **Write contiguously** when possible (avoid random access) +4. **Use collective I/O** in parallel applications + +### NetCDF-4 Features +1. **Use groups** to organize related variables +2. **Compound types** for structured data (like C structs) +3. **Compression** is transparent to readers +4. **Checksums** ensure data integrity + +## Tutorial Topics + +The NetCDF-C tutorial covers these key areas: + +### Data Model +- **Classic Model**: Dimensions, variables, attributes +- **Enhanced Model**: Groups, user-defined types, multiple unlimited dimensions +- **Unlimited Dimensions**: Growing datasets (time series) +- **Strings**: NC_STRING type in NetCDF-4 + +### File Operations +- **Creating Files**: Define mode vs data mode +- **Reading Known Structure**: When you know the schema +- **Reading Unknown Structure**: Generic file inspection +- **Subsets**: Hyperslabs, strides, mapped access + +### Advanced Topics +- **Error Handling**: Return codes and nc_strerror() +- **HDF5 Interoperability**: Reading HDF5 files as NetCDF +- **Parallel I/O**: MPI-based parallel access +- **Fill Values**: Handling missing/unwritten data + +## Command-Line Tools + +### ncdump - Examine Files +```bash +# View file structure +ncdump -h file.nc + +# View data +ncdump file.nc + +# View as CDL +ncdump -c file.nc +``` + +### ncgen - Generate Files from CDL +```bash +# Create NetCDF file from CDL +ncgen -o output.nc input.cdl + +# Create NetCDF-4 file +ncgen -k nc4 -o output.nc input.cdl +``` + +### nccopy - Copy and Convert +```bash +# Convert NetCDF-3 to NetCDF-4 with compression +nccopy -k nc4 -d 5 input.nc output.nc + +# Rechunk file +nccopy -c "var:10,20,30" input.nc output.nc +``` + +## Example File Locations + +In the NetCDF-C source tree: +- `examples/C/` - C examples +- `examples/CDL/` - CDL files for ncgen +- `nc_test/` - Test programs (also good examples) +- `nc_test4/` - NetCDF-4 test programs + +## Additional Resources + +**Official Documentation**: +- Tutorial: https://docs.unidata.ucar.edu/netcdf-c/current/tutorial_8dox.html +- Examples: https://docs.unidata.ucar.edu/netcdf-c/current/examples1.html +- API Reference: https://docs.unidata.ucar.edu/netcdf-c/current/modules.html + +**CF Conventions**: +- http://cfconventions.org/ - Climate and Forecast metadata conventions + +**Best Practices**: +- Use coordinate variables for dimensions +- Include units attributes +- Add descriptive global attributes +- Follow CF conventions when applicable +- Enable compression for large datasets +- Use chunking for subsetting access patterns diff --git a/windsurf-harnett/skills/netcdf-architecture/references/FORTRAN-INTERFACE.md b/windsurf-harnett/skills/netcdf-architecture/references/FORTRAN-INTERFACE.md new file mode 100644 index 000000000..099ee436f --- /dev/null +++ b/windsurf-harnett/skills/netcdf-architecture/references/FORTRAN-INTERFACE.md @@ -0,0 +1,379 @@ +# NetCDF Fortran 90 Interface Guide + +## Overview + +The NetCDF Fortran 90 interface provides a modern Fortran API to the NetCDF library. It wraps the underlying C library with type-safe Fortran 90 modules and interfaces. + +**Primary Module**: `netcdf` (defined in `netcdf.f90`) + +**Documentation**: https://docs.unidata.ucar.edu/netcdf-fortran/current/f90_The-NetCDF-Fortran-90-Interface-Guide.html + +## Key Concepts + +### Operating Modes + +NetCDF datasets operate in one of two modes: + +- **Define Mode**: Create dimensions, variables, and attributes. Cannot read/write variable data. +- **Data Mode**: Access data and modify existing attributes. Cannot create new dimensions, variables, or attributes. + +### Identifiers + +- **NetCDF ID**: Small non-negative integer returned when opening/creating a dataset +- **Variable ID**: Integer identifying a variable (1, 2, 3... in order of definition) +- **Dimension ID**: Integer identifying a dimension +- **Attribute**: Identified by name and associated variable/dataset + +## Common Usage Patterns + +### 1. Creating a NetCDF Dataset + +```fortran +use netcdf +implicit none + +integer :: ncid, status +integer :: x_dimid, y_dimid, time_dimid +integer :: temp_varid + +! Create dataset +status = nf90_create("output.nc", NF90_CLOBBER, ncid) +if (status /= NF90_NOERR) call handle_error(status) + +! Define dimensions (in define mode by default) +status = nf90_def_dim(ncid, "x", 100, x_dimid) +status = nf90_def_dim(ncid, "y", 50, y_dimid) +status = nf90_def_dim(ncid, "time", NF90_UNLIMITED, time_dimid) + +! Define variables +status = nf90_def_var(ncid, "temperature", NF90_FLOAT, & + [x_dimid, y_dimid, time_dimid], temp_varid) + +! Add attributes +status = nf90_put_att(ncid, temp_varid, "units", "celsius") + +! End define mode, enter data mode +status = nf90_enddef(ncid) + +! Write data +status = nf90_put_var(ncid, temp_varid, data_array) + +! Close dataset +status = nf90_close(ncid) +``` + +### 2. Reading a Dataset with Known Names + +```fortran +use netcdf +implicit none + +integer :: ncid, varid, status +real, allocatable :: data(:,:,:) + +! Open dataset for reading +status = nf90_open("input.nc", NF90_NOWRITE, ncid) + +! Get variable ID from name +status = nf90_inq_varid(ncid, "temperature", varid) + +! Read data +status = nf90_get_var(ncid, varid, data) + +! Close dataset (optional for read-only) +status = nf90_close(ncid) +``` + +### 3. Reading a Dataset with Unknown Names + +```fortran +use netcdf +implicit none + +integer :: ncid, status +integer :: ndims, nvars, ngatts, unlimdimid +integer :: i, varid +character(len=NF90_MAX_NAME) :: varname + +! Open dataset +status = nf90_open("input.nc", NF90_NOWRITE, ncid) + +! Inquire about dataset contents +status = nf90_inquire(ncid, ndims, nvars, ngatts, unlimdimid) + +! Loop through variables +do i = 1, nvars + status = nf90_inquire_variable(ncid, i, name=varname) + print *, "Variable: ", trim(varname) +end do + +status = nf90_close(ncid) +``` + +## Core Function Categories + +### Dataset Operations + +- **`NF90_CREATE`**: Create a new NetCDF dataset +- **`NF90_OPEN`**: Open an existing dataset +- **`NF90_CLOSE`**: Close an open dataset +- **`NF90_REDEF`**: Enter define mode +- **`NF90_ENDDEF`**: Exit define mode, enter data mode +- **`NF90_SYNC`**: Synchronize dataset to disk +- **`NF90_ABORT`**: Close dataset without saving changes +- **`NF90_INQUIRE`**: Get information about dataset +- **`NF90_SET_FILL`**: Set fill mode for variables + +### Dimension Operations + +- **`NF90_DEF_DIM`**: Define a dimension +- **`NF90_INQ_DIMID`**: Get dimension ID from name +- **`NF90_INQUIRE_DIMENSION`**: Get dimension information +- **`NF90_RENAME_DIM`**: Rename a dimension + +### Variable Operations + +- **`NF90_DEF_VAR`**: Define a variable +- **`NF90_INQ_VARID`**: Get variable ID from name +- **`NF90_INQUIRE_VARIABLE`**: Get variable information +- **`NF90_PUT_VAR`**: Write data to a variable +- **`NF90_GET_VAR`**: Read data from a variable +- **`NF90_RENAME_VAR`**: Rename a variable +- **`NF90_DEF_VAR_FILL`**: Define fill parameters +- **`NF90_INQ_VAR_FILL`**: Get fill parameters +- **`NF90_DEF_VAR_FILTER`**: Define filter/compression +- **`NF90_INQ_VAR_FILTER`**: Get filter information + +### Attribute Operations + +- **`NF90_PUT_ATT`**: Write an attribute +- **`NF90_GET_ATT`**: Read an attribute +- **`NF90_INQ_ATTNAME`**: Get attribute name from number +- **`NF90_INQUIRE_ATTRIBUTE`**: Get attribute information +- **`NF90_RENAME_ATT`**: Rename an attribute +- **`NF90_DEL_ATT`**: Delete an attribute +- **`NF90_COPY_ATT`**: Copy attribute to another variable + +### Group Operations (NetCDF-4) + +- **`NF90_DEF_GRP`**: Create a group +- **`NF90_INQ_NCID`**: Get group ID from name +- **`NF90_INQ_GRPS`**: Get child group IDs +- **`NF90_INQ_GRPNAME`**: Get group name +- **`NF90_RENAME_GRP`**: Rename a group + +### User-Defined Types (NetCDF-4) + +- **`NF90_DEF_COMPOUND`**: Define compound type +- **`NF90_DEF_VLEN`**: Define variable-length type +- **`NF90_DEF_OPAQUE`**: Define opaque type +- **`NF90_DEF_ENUM`**: Define enumeration type + +## Data Types + +### NetCDF External Types and Fortran Constants + +| NetCDF Type | Fortran 90 Constant | Bits | +|-------------|---------------------|------| +| byte | NF90_BYTE | 8 | +| char | NF90_CHAR | 8 | +| short | NF90_SHORT | 16 | +| int | NF90_INT | 32 | +| float | NF90_FLOAT | 32 | +| double | NF90_DOUBLE | 64 | +| ubyte | NF90_UBYTE | 8 | +| ushort | NF90_USHORT | 16 | +| uint | NF90_UINT | 32 | +| int64 | NF90_INT64 | 64 | +| uint64 | NF90_UINT64 | 64 | +| string | NF90_STRING | - | + +## Variable I/O Flexibility + +The `NF90_PUT_VAR` and `NF90_GET_VAR` functions support flexible data access: + +### Basic Usage +```fortran +! Write entire array +status = nf90_put_var(ncid, varid, data_array) + +! Read entire array +status = nf90_get_var(ncid, varid, data_array) +``` + +### Subsetting with start/count +```fortran +! Write a subset starting at index (10,20) with size (5,10) +status = nf90_put_var(ncid, varid, data_array, & + start=[10,20], count=[5,10]) +``` + +### Strided Access +```fortran +! Read every other element +status = nf90_get_var(ncid, varid, data_array, & + start=[1,1], count=[50,25], stride=[2,2]) +``` + +### Mapped Access +```fortran +! Non-contiguous memory mapping +status = nf90_put_var(ncid, varid, data_array, & + start=[1,1], count=[10,10], map=[1,100]) +``` + +## Error Handling + +All NetCDF Fortran functions return an integer status code: + +```fortran +integer :: status + +status = nf90_open("file.nc", NF90_NOWRITE, ncid) +if (status /= NF90_NOERR) then + print *, trim(nf90_strerror(status)) + stop "Error opening file" +end if +``` + +### Common Error Codes + +- **`NF90_NOERR`**: No error +- **`NF90_EBADID`**: Invalid NetCDF ID +- **`NF90_ENOTVAR`**: Variable not found +- **`NF90_EINDEFINE`**: Operation not allowed in define mode +- **`NF90_ENOTINDEFINE`**: Operation requires define mode +- **`NF90_EINVAL`**: Invalid argument + +### Error Message Function + +**`NF90_STRERROR(status)`**: Returns descriptive error message string + +## Mode Flags + +### File Creation/Opening Modes + +- **`NF90_NOWRITE`**: Open read-only +- **`NF90_WRITE`**: Open for writing +- **`NF90_CLOBBER`**: Overwrite existing file +- **`NF90_NOCLOBBER`**: Fail if file exists +- **`NF90_SHARE`**: Disable buffering for immediate writes +- **`NF90_NETCDF4`**: Create NetCDF-4/HDF5 file +- **`NF90_CLASSIC_MODEL`**: Use classic data model with NetCDF-4 +- **`NF90_64BIT_OFFSET`**: Use CDF-2 format (large file support) +- **`NF90_64BIT_DATA`**: Use CDF-5 format (large variable support) + +### Variable Storage Options (NetCDF-4) + +- **`NF90_CHUNKED`**: Use chunked storage +- **`NF90_CONTIGUOUS`**: Use contiguous storage +- **`NF90_COMPACT`**: Use compact storage (small variables) + +### Compression Options (NetCDF-4) + +```fortran +! Define variable with compression +status = nf90_def_var(ncid, "data", NF90_FLOAT, dimids, varid, & + deflate_level=6, shuffle=.true.) +``` + +## Parallel I/O (NetCDF-4 with MPI) + +```fortran +use netcdf +use mpi +implicit none + +integer :: ncid, varid, status +integer :: comm, info + +! Initialize MPI +call MPI_Init(ierr) +comm = MPI_COMM_WORLD +info = MPI_INFO_NULL + +! Create parallel file +status = nf90_create_par("parallel.nc", & + IOR(NF90_NETCDF4, NF90_MPIIO), & + comm, info, ncid) + +! Set collective access +status = nf90_var_par_access(ncid, varid, NF90_COLLECTIVE) + +! Each process writes its portion +status = nf90_put_var(ncid, varid, local_data, & + start=[my_start], count=[my_count]) + +status = nf90_close(ncid) +call MPI_Finalize(ierr) +``` + +## Best Practices + +### 1. Always Check Return Status +```fortran +if (status /= NF90_NOERR) call handle_error(status) +``` + +### 2. Close Files Explicitly +```fortran +status = nf90_close(ncid) +``` + +### 3. Use NF90_SYNC for Critical Data +```fortran +! Ensure data is written to disk +status = nf90_sync(ncid) +``` + +### 4. Minimize Define Mode Transitions +Define all dimensions, variables, and attributes before entering data mode to avoid performance overhead. + +### 5. Use Chunking for Large Arrays (NetCDF-4) +```fortran +status = nf90_def_var(ncid, "data", NF90_FLOAT, dimids, varid, & + chunksizes=[100,100,1]) +``` + +### 6. Enable Compression for Large Datasets +```fortran +status = nf90_def_var(ncid, "data", NF90_FLOAT, dimids, varid, & + deflate_level=4, shuffle=.true.) +``` + +## Relationship to C Library + +The Fortran 90 interface is a wrapper around the NetCDF-C library: + +1. **Module**: `netcdf.f90` provides Fortran 90 interfaces +2. **Binding**: Calls C functions via ISO_C_BINDING +3. **Naming**: Fortran functions use `NF90_` prefix (C uses `nc_`) +4. **Types**: Fortran constants map to C types +5. **Arrays**: Fortran column-major order vs C row-major (handled internally) + +## Integration with NetCDF-C Architecture + +The Fortran interface sits on top of the C library dispatch architecture: + +``` +Fortran 90 Application + ↓ +netcdf.f90 module (NF90_* functions) + ↓ +ISO_C_BINDING layer + ↓ +NetCDF-C API (nc_* functions) + ↓ +libdispatch (dispatch tables) + ↓ +Format-specific implementations +(NC3, HDF5, Zarr, DAP) +``` + +All format support, dispatch routing, and I/O operations are handled by the underlying C library. + +## Additional Resources + +- **Main Guide**: https://docs.unidata.ucar.edu/netcdf-fortran/current/ +- **API Reference**: https://docs.unidata.ucar.edu/netcdf-fortran/current/f90_The-NetCDF-Fortran-90-Interface-Guide.html +- **Examples**: https://docs.unidata.ucar.edu/netcdf-fortran/current/examples.html diff --git a/windsurf-harnett/skills/netcdf-architecture/references/UDF-PLUGINS.md b/windsurf-harnett/skills/netcdf-architecture/references/UDF-PLUGINS.md new file mode 100644 index 000000000..89bbf2fca --- /dev/null +++ b/windsurf-harnett/skills/netcdf-architecture/references/UDF-PLUGINS.md @@ -0,0 +1,551 @@ +# NetCDF-C User-Defined Format (UDF) Plugin Development + +This reference provides comprehensive guidance for developing UDF plugins for NetCDF-C. + +## Overview + +User-Defined Formats (UDFs) allow developers to extend NetCDF-C with custom file formats and storage backends through a plugin system. The library provides 10 independent UDF slots (UDF0-UDF9) that can be registered programmatically or via RC file configuration. + +## Plugin Architecture + +A UDF plugin consists of three main components: + +1. **Dispatch Table**: `NC_Dispatch` structure with function pointers implementing the netCDF API +2. **Initialization Function**: Called during plugin loading to register the dispatch table +3. **Format-Specific Code**: Implementation of file I/O and data operations + +## Plugin Lifecycle + +1. Library initialization (`nc_initialize()`) +2. RC file parsing (if configured) +3. Plugin library loading (`dlopen`/`LoadLibrary`) +4. Init function location (`dlsym`/`GetProcAddress`) +5. Init function execution +6. Dispatch table registration via `nc_def_user_format()` +7. Plugin remains loaded for process lifetime + +## Dispatch Table Implementation + +### Required Fields + +```c +typedef struct NC_Dispatch { + int model; /* NC_FORMATX_UDF0 through NC_FORMATX_UDF9 */ + int dispatch_version; /* Must be NC_DISPATCH_VERSION */ + + /* Function pointers for all netCDF operations (~70 total) */ + int (*create)(...); + int (*open)(...); + int (*close)(...); + int (*get_vara)(...); + int (*put_vara)(...); + /* ... many more functions ... */ +} NC_Dispatch; +``` + +**Location**: `include/netcdf_dispatch.h` + +### Minimal Example + +```c +#include "netcdf_dispatch.h" + +static NC_Dispatch my_dispatcher = { + NC_FORMATX_UDF0, /* Use UDF slot 0 */ + NC_DISPATCH_VERSION, /* Current ABI version */ + + NC_RO_create, /* Read-only: use predefined function */ + my_open, /* Custom open function */ + NC_RO_redef, + NC_RO__enddef, + NC_RO_sync, + my_abort, + my_close, + NC_RO_set_fill, + my_inq_format, + my_inq_format_extended, + + /* Inquiry functions - can use NC4_* defaults */ + NC4_inq, + NC4_inq_type, + NC4_inq_dimid, + NC4_inq_varid, + NC4_inq_unlimdim, + NC4_inq_grpname, + NC4_inq_grpname_full, + NC4_inq_grp_parent, + NC4_inq_grp_full_ncid, + NC4_inq_varids, + NC4_inq_dimids, + NC4_inq_typeids, + NC4_inq_type_equal, + NC4_inq_user_type, + NC4_inq_typeid, + + /* Variable I/O */ + my_get_vara, + NC_RO_put_vara, /* Read-only */ + NCDEFAULT_get_vars, /* Use default strided implementation */ + NC_RO_put_vars, + NCDEFAULT_get_varm, /* Use default mapped implementation */ + NC_RO_put_varm, + + /* Attributes */ + NC4_get_att, + NC_RO_put_att, + + /* Dimensions */ + NC4_inq_dim, + NC_RO_def_dim, + NC4_inq_unlimdims, + NC_RO_rename_dim, + + /* Variables */ + NC4_inq_var_all, + NC_RO_def_var, + NC_RO_rename_var, + NC4_var_par_access, + NC_RO_def_var_fill, + + /* NetCDF-4 features not supported */ + NC_NOTNC4_show_metadata, + NC_NOTNC4_inq_grps, + NC_NOTNC4_inq_ncid, + NC_NOTNC4_inq_format_extended, + NC_NOTNC4_inq_var_all, + NC_NOTNC4_def_grp, + NC_NOTNC4_rename_grp, + NC_NOTNC4_inq_user_type, + NC_NOTNC4_def_compound, + NC_NOTNC4_insert_compound, + NC_NOTNC4_insert_array_compound, + NC_NOTNC4_inq_compound_field, + NC_NOTNC4_inq_compound_fieldindex, + NC_NOTNC4_def_vlen, + NC_NOTNC4_def_enum, + NC_NOTNC4_def_opaque, + NC_NOTNC4_def_var_deflate, + NC_NOTNC4_def_var_fletcher32, + NC_NOTNC4_def_var_chunking, + NC_NOTNC4_def_var_endian, + NC_NOTNC4_def_var_filter, + NC_NOTNC4_set_var_chunk_cache, + NC_NOTNC4_get_var_chunk_cache, + NC_NOTNC4_inq_var_filter_ids, + NC_NOTNC4_inq_var_filter_info, + NC_NOTNC4_def_var_quantize, + NC_NOTNC4_inq_var_quantize, +}; +``` + +## Pre-defined Functions + +Use these for operations your format doesn't support: + +### Read-Only Stubs + +**File**: `libdispatch/dreadonly.c` + +Returns `NC_EPERM` (operation not permitted): +- `NC_RO_create` - File creation +- `NC_RO_redef` - Enter define mode +- `NC_RO__enddef` - Leave define mode +- `NC_RO_sync` - Synchronize to disk +- `NC_RO_set_fill` - Set fill mode +- `NC_RO_def_dim` - Define dimension +- `NC_RO_def_var` - Define variable +- `NC_RO_rename_dim` - Rename dimension +- `NC_RO_rename_var` - Rename variable +- `NC_RO_put_att` - Write attribute +- `NC_RO_del_att` - Delete attribute +- `NC_RO_put_vara` - Write variable data +- `NC_RO_put_vars` - Write strided data +- `NC_RO_put_varm` - Write mapped data +- `NC_RO_def_var_fill` - Define fill value + +### Not NetCDF-4 Stubs + +**File**: `libdispatch/dnotnc4.c` + +Returns `NC_ENOTNC4` (not a NetCDF-4 file): +- `NC_NOTNC4_def_grp` - Define group +- `NC_NOTNC4_rename_grp` - Rename group +- `NC_NOTNC4_def_compound` - Define compound type +- `NC_NOTNC4_def_vlen` - Define variable-length type +- `NC_NOTNC4_def_enum` - Define enumeration type +- `NC_NOTNC4_def_opaque` - Define opaque type +- `NC_NOTNC4_def_var_deflate` - Define compression +- `NC_NOTNC4_def_var_fletcher32` - Define checksums +- `NC_NOTNC4_def_var_chunking` - Define chunking +- `NC_NOTNC4_def_var_endian` - Define endianness +- `NC_NOTNC4_def_var_filter` - Define filter +- `NC_NOTNC4_def_var_quantize` - Define quantization + +### Default Implementations + +**File**: `libdispatch/dvar.c` + +Generic implementations built on simpler operations: +- `NCDEFAULT_get_vars` - Strided read using `get_vara` +- `NCDEFAULT_put_vars` - Strided write using `put_vara` +- `NCDEFAULT_get_varm` - Mapped read using `get_vars` +- `NCDEFAULT_put_varm` - Mapped write using `put_vars` + +### NetCDF-4 Inquiry Functions + +**Files**: `libsrc4/*.c` + +Use internal metadata model for inquiry operations: +- `NC4_inq` - Inquire about file +- `NC4_inq_type` - Inquire about type +- `NC4_inq_dimid` - Get dimension ID +- `NC4_inq_varid` - Get variable ID +- `NC4_inq_unlimdim` - Get unlimited dimension +- `NC4_inq_grpname` - Get group name +- `NC4_inq_varids` - Get all variable IDs +- `NC4_inq_dimids` - Get all dimension IDs +- `NC4_get_att` - Get attribute value +- `NC4_inq_var_all` - Get all variable info + +## Initialization Function + +### Function Signature + +```c +int plugin_init(void); +``` + +### Requirements + +1. Must be exported (not static) +2. Must call `nc_def_user_format()` to register dispatch table +3. Should return `NC_NOERR` on success, error code on failure +4. Name must match RC file INIT key + +### Example Implementation + +```c +#include + +/* Your dispatch table */ +extern NC_Dispatch my_dispatcher; + +/* Initialization function - must be exported */ +int my_plugin_init(void) +{ + int ret; + + /* Register dispatch table with magic number */ + ret = nc_def_user_format(NC_UDF0 | NC_NETCDF4, + &my_dispatcher, + "MYFMT"); + if (ret != NC_NOERR) + return ret; + + /* Additional initialization if needed */ + /* ... */ + + return NC_NOERR; +} +``` + +## Implementing Key Dispatch Functions + +### Open Function + +```c +int my_open(const char *path, int mode, int basepe, size_t *chunksizehintp, + void *parameters, const NC_Dispatch *dispatch, int ncid) +{ + /* 1. Open your file format */ + /* 2. Populate internal metadata structures */ + /* 3. Store format-specific data in NC->dispatchdata */ + /* 4. Return NC_NOERR on success */ + + return NC_NOERR; +} +``` + +### Close Function + +```c +int my_close(int ncid, void *v) +{ + /* 1. Clean up resources */ + /* 2. Close file handles */ + /* 3. Free format-specific data */ + + return NC_NOERR; +} +``` + +### Abort Function + +```c +int my_abort(int ncid, void *v) +{ + /* 1. Discard any pending changes */ + /* 2. Clean up resources */ + /* 3. Close file handles */ + + return NC_NOERR; +} +``` + +### Format Inquiry Functions + +```c +int my_inq_format(int ncid, int *formatp) +{ + if (formatp) + *formatp = NC_FORMAT_NETCDF4; /* Or appropriate format */ + return NC_NOERR; +} + +int my_inq_format_extended(int ncid, int *formatp, int *modep) +{ + if (formatp) + *formatp = NC_FORMATX_UDF0; + if (modep) + *modep = NC_UDF0 | NC_NETCDF4; + return NC_NOERR; +} +``` + +### Variable I/O Functions + +```c +int my_get_vara(int ncid, int varid, const size_t *start, + const size_t *count, void *value, nc_type memtype) +{ + /* 1. Validate parameters */ + /* 2. Read data from your format */ + /* 3. Convert to requested memory type if needed */ + /* 4. Copy to value buffer */ + + return NC_NOERR; +} +``` + +## Building Plugins + +### Unix/Linux/macOS + +**Makefile**: +```makefile +CC = gcc +CFLAGS = -fPIC -I/usr/local/include +LDFLAGS = -shared -L/usr/local/lib -lnetcdf + +myplugin.so: myplugin.c + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< + +install: + cp myplugin.so /usr/local/lib/ +``` + +**Command line**: +```bash +gcc -shared -fPIC -I/usr/local/include -o myplugin.so myplugin.c -lnetcdf +``` + +### Windows + +**Command line**: +```batch +cl /LD /I"C:\netcdf\include" myplugin.c /link /LIBPATH:"C:\netcdf\lib" netcdf.lib +``` + +### CMake + +```cmake +cmake_minimum_required(VERSION 3.10) +project(MyPlugin) + +find_package(netCDF REQUIRED) + +add_library(myplugin SHARED myplugin.c) +target_link_libraries(myplugin netCDF::netcdf) +target_include_directories(myplugin PRIVATE ${netCDF_INCLUDE_DIRS}) + +install(TARGETS myplugin LIBRARY DESTINATION lib) +``` + +## Testing Plugins + +### Unit Testing + +```c +/* test_plugin.c */ +#include +#include + +extern NC_Dispatch my_dispatcher; +extern int my_plugin_init(void); + +int main() { + int ret; + NC_Dispatch *disp; + + /* Test initialization */ + ret = my_plugin_init(); + assert(ret == NC_NOERR); + + /* Verify registration */ + ret = nc_inq_user_format(NC_UDF0, &disp, NULL); + assert(ret == NC_NOERR); + assert(disp == &my_dispatcher); + + printf("Plugin tests passed\n"); + return 0; +} +``` + +### Integration Testing + +```c +/* test_integration.c */ +#include + +int main() { + int ncid, ret; + + /* Initialize and register plugin */ + my_plugin_init(); + + /* Test file operations */ + ret = nc_open("testfile.dat", NC_UDF0, &ncid); + if (ret != NC_NOERR) { + fprintf(stderr, "Open failed: %s\n", nc_strerror(ret)); + return 1; + } + + /* Test operations */ + int format; + nc_inq_format(ncid, &format); + + nc_close(ncid); + printf("Integration test passed\n"); + return 0; +} +``` + +### RC File Testing + +Create `.ncrc`: +```ini +NETCDF.UDF0.LIBRARY=/path/to/myplugin.so +NETCDF.UDF0.INIT=my_plugin_init +NETCDF.UDF0.MAGIC=MYFMT +``` + +Test automatic loading: +```c +int main() { + /* Plugin loads automatically during nc_initialize() */ + int ncid; + nc_open("file_with_magic.dat", 0, &ncid); /* Auto-detects format */ + nc_close(ncid); + return 0; +} +``` + +## Debugging + +### Enable NetCDF Logging + +```bash +export NC_LOG_LEVEL=3 +./test_program +``` + +### Check Symbol Exports + +**Unix**: +```bash +nm -D libmyplugin.so | grep init +``` + +**Windows**: +```batch +dumpbin /EXPORTS myplugin.dll +``` + +### GDB Debugging + +```bash +gdb ./test_program +(gdb) break my_plugin_init +(gdb) run +(gdb) backtrace +``` + +### Common Issues + +**Plugin not loaded**: +- Check RC file syntax +- Verify both LIBRARY and INIT are present +- Use absolute path for LIBRARY + +**Init function not found**: +- Ensure function is not static +- Check function name matches INIT key +- Verify symbol is exported + +**ABI version mismatch**: +- Recompile against current netCDF-C headers +- Check `NC_DISPATCH_VERSION` value + +## Best Practices + +1. **Error Handling**: Return appropriate `NC_E*` error codes +2. **Memory Management**: Clean up in close/abort functions +3. **Thread Safety**: Use thread-safe operations if needed +4. **Logging**: Use `nclog` functions for diagnostic output +5. **Documentation**: Document your format and API +6. **Testing**: Test all code paths thoroughly +7. **Versioning**: Version your plugin and document compatibility + +## Magic Numbers + +### How They Work + +When `nc_open()` is called without a specific format flag: +1. File's first bytes are read +2. Compared against all registered magic numbers +3. If match found, corresponding UDF dispatcher is used + +### Best Practices + +- Use unique, distinctive strings (4-8 bytes recommended) +- Place at beginning of file format +- Avoid conflicts with existing formats: + - NetCDF-3: "CDF\001", "CDF\002", "CDF\005" + - HDF5/NetCDF-4: "\211HDF\r\n\032\n" +- Maximum length: `NC_MAX_MAGIC_NUMBER_LEN` bytes + +### Example + +```c +/* File format with magic number */ +FILE *fp = fopen("mydata.dat", "wb"); +fwrite("MYDATA", 1, 6, fp); /* Magic number */ +/* ... write your data ... */ +fclose(fp); + +/* Register UDF with magic number */ +nc_def_user_format(NC_UDF0 | NC_NETCDF4, &my_dispatcher, "MYDATA"); + +/* Open automatically detects format */ +int ncid; +nc_open("mydata.dat", 0, &ncid); /* No mode flag needed! */ +``` + +## Reference Files + +- **Dispatch table definition**: `include/netcdf_dispatch.h` +- **Pre-defined functions**: `libdispatch/dreadonly.c`, `libdispatch/dnotnc*.c` +- **Example implementations**: `libhdf5/hdf5dispatch.c`, `libsrc/nc3dispatch.c` +- **Test plugins**: `nc_test4/test_plugin_lib.c` +- **Plugin loading**: `libdispatch/dutil.c`, `libdispatch/drc.c` +- **Registration API**: `libdispatch/ddispatch.c` diff --git a/windsurf-harnett/skills/netcdf-java/skill.md b/windsurf-harnett/skills/netcdf-java/skill.md new file mode 100644 index 000000000..42286c453 --- /dev/null +++ b/windsurf-harnett/skills/netcdf-java/skill.md @@ -0,0 +1,315 @@ +# NetCDF-Java Library + +## Overview +The netCDF-Java library is a 100% Java framework for reading and writing scientific data formats. It implements the Common Data Model (CDM), which is an abstract data model that merges netCDF, OPeNDAP, and HDF5 data models to create a unified API for accessing many types of scientific data. + +**Key Capabilities:** +- Read netCDF-3, netCDF-4, HDF5, GRIB, BUFR, and many other scientific data formats +- Write netCDF-3 files natively +- Write netCDF-4 files via JNI to netCDF-C library +- Access remote datasets via OPeNDAP and other protocols +- Support for NcML (NetCDF Markup Language) for metadata manipulation and aggregation +- Coordinate system identification and georeferencing +- Scientific feature type support (grids, point data, radial data, etc.) + +## Documentation and Resources +- **GitHub Repository:** https://github.com/Unidata/netcdf-java +- **Main Documentation:** https://docs.unidata.ucar.edu/netcdf-java/current/userguide/ +- **API Reference:** Available through Maven artifacts +- **License:** BSD-3 (as of version 5.0) +- **Maven Repository:** https://artifacts.unidata.ucar.edu/ + +## Common Data Model (CDM) Architecture + +The CDM has three layers that build on each other: + +### 1. Data Access Layer (Syntactic Layer) +Handles data reading and writing through: +- **NetcdfFile:** Read-only access to datasets +- **NetcdfFiles:** Static methods for opening files +- **IOServiceProvider:** Interface for format-specific implementations +- **Variable, Dimension, Attribute, Group, Structure:** Metadata objects + +### 2. Coordinate System Layer +Identifies coordinates of data arrays: +- General coordinate concepts for scientific data +- Specialized georeferencing coordinate systems for Earth Science +- CoordinateAxis and CoordinateSystem objects + +### 3. Scientific Feature Types Layer +Specialized methods for specific data types: +- Grids +- Point data +- Radial data (radar, lidar) +- Station data +- Trajectory data + +## Basic Usage Patterns + +### Opening and Reading Files + +```java +// Open a NetCDF file +try (NetcdfFile ncfile = NetcdfFiles.open(pathToFile)) { + // File is automatically closed when try block exits + + // Find a variable by name + Variable v = ncfile.findVariable("temperature"); + if (v == null) { + System.err.println("Variable not found"); + return; + } + + // Read all data from the variable + Array data = v.read(); + + // Read a subset using section specification + // Format: "dim1_start:dim1_end:dim1_stride, dim2_start:dim2_end, ..." + Array subset = v.read("0:10:2, :, 5"); + +} catch (IOException e) { + e.printStackTrace(); +} +``` + +### Working with Metadata + +```java +try (NetcdfFile ncfile = NetcdfFiles.open(pathToFile)) { + // List all variables + for (Variable var : ncfile.getVariables()) { + System.out.println("Variable: " + var.getFullName()); + System.out.println(" Type: " + var.getDataType()); + System.out.println(" Shape: " + Arrays.toString(var.getShape())); + + // Get attributes + for (Attribute attr : var.attributes()) { + System.out.println(" Attribute: " + attr.getFullName() + " = " + attr.getValue()); + } + } + + // List dimensions + for (Dimension dim : ncfile.getDimensions()) { + System.out.println("Dimension: " + dim.getFullName() + " = " + dim.getLength()); + } + + // Get global attributes + for (Attribute attr : ncfile.getGlobalAttributes()) { + System.out.println("Global: " + attr.getFullName() + " = " + attr.getValue()); + } + +} catch (IOException e) { + e.printStackTrace(); +} +``` + +### Reading Data Arrays + +```java +// Read scalar data +Variable scalarVar = ncfile.findVariable("scalar_value"); +double scalarValue = scalarVar.readScalarDouble(); + +// Read 1D array +Variable var1d = ncfile.findVariable("time"); +Array timeData = var1d.read(); +int[] shape = timeData.getShape(); +for (int i = 0; i < shape[0]; i++) { + double value = timeData.getDouble(i); + System.out.println("time[" + i + "] = " + value); +} + +// Read multidimensional array +Variable var3d = ncfile.findVariable("temperature"); +Array tempData = var3d.read(); +Index index = tempData.getIndex(); +int[] shape3d = tempData.getShape(); +for (int t = 0; t < shape3d[0]; t++) { + for (int y = 0; y < shape3d[1]; y++) { + for (int x = 0; x < shape3d[2]; x++) { + double value = tempData.getDouble(index.set(t, y, x)); + } + } +} +``` + +### Array Section Syntax + +NetCDF-Java uses Fortran 90 array section syntax with zero-based indexing: +- `":"` - all elements in dimension +- `"start:end"` - elements from start to end (inclusive) +- `"start:end:stride"` - elements with stride +- Example: `"0:10:2, :, 5"` means first dimension 0-10 with stride 2, all of second dimension, element 5 of third dimension + +## NetCDF Markup Language (NcML) + +NcML is an XML representation of netCDF metadata that can: +- Describe netCDF file structure (similar to CDL) +- Modify existing datasets (add/change attributes, variables) +- Create virtual datasets through aggregation +- Define coordinate systems + +### Basic NcML Example + +```xml + + + + + + + + + +``` + +### NcML Aggregation + +NcML supports several aggregation types: +- **joinExisting:** Concatenate along existing dimension +- **joinNew:** Create new dimension for aggregation +- **union:** Combine variables from multiple files +- **tiled:** Aggregate multidimensional tiles + +```xml + + + + + +``` + +## Advanced Features + +### Opening Remote Files + +```java +// OPeNDAP URL +NetcdfFile ncfile = NetcdfFiles.open("https://server.org/dods/dataset"); + +// HTTP Server +NetcdfFile ncfile = NetcdfFiles.open("https://server.org/data/file.nc"); + +// AWS S3 +NetcdfFile ncfile = NetcdfFiles.open("cdms3://bucket-name/path/to/file.nc"); +``` + +### Using NetcdfDataset for Enhanced Features + +```java +// NetcdfDataset provides coordinate system support and NcML processing +try (NetcdfDataset ncd = NetcdfDatasets.openDataset(pathToFile)) { + // Access coordinate systems + for (CoordinateSystem cs : ncd.getCoordinateSystems()) { + System.out.println("Coordinate System: " + cs.getName()); + for (CoordinateAxis axis : cs.getCoordinateAxes()) { + System.out.println(" Axis: " + axis.getFullName()); + } + } +} +``` + +### Disk Caching + +NetCDF-Java automatically handles compressed files (.Z, .zip, .gzip, .gz, .bz2) by uncompressing them to a disk cache before opening. + +## File Format Support + +The library can read many formats through IOServiceProvider implementations: +- NetCDF-3 (classic and 64-bit offset) +- NetCDF-4 (HDF5-based) +- HDF4 and HDF5 +- GRIB (GRIB-1 and GRIB-2) +- BUFR +- NEXRAD Level 2 and Level 3 +- OPeNDAP (DAP2 and DAP4) +- Many others + +**Note:** Some formats require optional modules to be included as Maven/Gradle artifacts. + +## Maven/Gradle Integration + +### Maven Example + +```xml + + edu.ucar + cdm-core + 5.5.3 + + + + + edu.ucar + netcdfAll + 5.5.3 + +``` + +### Gradle Example + +```gradle +dependencies { + implementation 'edu.ucar:cdm-core:5.5.3' + // or for all formats + implementation 'edu.ucar:netcdfAll:5.5.3' +} +``` + +## ToolsUI Application + +ToolsUI is a graphical application for browsing and debugging NetCDF files: +- Download: `toolsUI.jar` from netCDF-Java downloads page +- Run: `java -Xmx1g -jar toolsUI.jar` +- Features: Browse metadata, view data, test coordinate systems, debug IOSPs + +## Best Practices + +1. **Always use try-with-resources** to ensure files are properly closed +2. **Read metadata first** - structural metadata is loaded at open time, data is lazy-loaded +3. **Use section specifications** to read subsets of large arrays +4. **Check for null** when finding variables or attributes +5. **Use NetcdfDataset** when you need coordinate system support +6. **Cache remote files** for better performance with repeated access +7. **Use appropriate data types** - Array provides type-specific getters (getDouble, getFloat, etc.) + +## Common Pitfalls + +- **Zero-based indexing:** Unlike Fortran, Java uses zero-based array indexing +- **Write limitations:** Native Java can only write netCDF-3; netCDF-4 requires C library via JNI +- **Module dependencies:** Some file formats require additional Maven artifacts +- **Memory management:** Large arrays can consume significant memory; use sections when possible +- **Thread safety:** NetcdfFile objects are not thread-safe; use one per thread or synchronize access + +## Integration with THREDDS + +The THREDDS Data Server (TDS) is built on top of netCDF-Java and provides: +- Remote data access via OPeNDAP, WCS, WMS, HTTP +- Catalog services for dataset discovery +- Aggregation and virtual dataset support +- Metadata services + +## Version History + +- **Version 5.x:** Decoupled from TDS, BSD-3 license, modular architecture +- **Version 4.6 and earlier:** Combined with TDS in single repository +- **Current target:** Java 8 (community feedback being gathered for future versions) + +## When to Use NetCDF-Java + +Use netCDF-Java when you need to: +- Read scientific data in Java applications +- Support multiple file formats with a single API +- Work with remote datasets (OPeNDAP, HTTP, S3) +- Manipulate metadata without rewriting files (NcML) +- Aggregate multiple files into virtual datasets +- Access coordinate system information +- Build web services for scientific data (with THREDDS) + +## Related Technologies + +- **netCDF-C:** C library for netCDF, can be called via JNI for netCDF-4 writing +- **THREDDS Data Server:** Web server built on netCDF-Java +- **OPeNDAP:** Protocol for remote data access +- **CF Conventions:** Metadata conventions for climate and forecast data +- **NcML:** XML language for netCDF metadata and aggregation diff --git a/windsurf-harnett/skills/opendap/README.md b/windsurf-harnett/skills/opendap/README.md new file mode 100644 index 000000000..a7ac6e507 --- /dev/null +++ b/windsurf-harnett/skills/opendap/README.md @@ -0,0 +1,34 @@ +# OPeNDAP Skill + +This Windsurf skill provides comprehensive knowledge of OPeNDAP (Open-source Project for a Network Data Access Protocol) for accessing and serving scientific data over the internet. + +## What This Skill Provides + +- **OPeNDAP architecture** and client/server model +- **Data Access Protocol (DAP)** versions 2 and 4 +- **Constraint expressions** for subsetting remote data +- **Data model** and type system +- **URL construction** and service endpoints +- **Client integration** with NetCDF and other tools + +## Files + +- **SKILL.md** - Main skill file with OPeNDAP overview and quick reference +- **references/PROTOCOL.md** - DAP2 and DAP4 protocol details +- **references/CONSTRAINTS.md** - Constraint expression syntax and examples +- **references/DATA-MODEL.md** - OPeNDAP data types and structures +- **references/CLIENT-USAGE.md** - Using OPeNDAP with various clients + +## When to Use + +Use this skill when: +- Accessing remote scientific data via OPeNDAP URLs +- Writing programs that use OPeNDAP services +- Constructing constraint expressions for data subsetting +- Understanding DAP responses (DDS, DAS, DMR) +- Integrating OPeNDAP with NetCDF applications +- Debugging OPeNDAP client/server issues + +## Version + +Current version: 1.0 (January 19, 2026) diff --git a/windsurf-harnett/skills/opendap/SKILL.md b/windsurf-harnett/skills/opendap/SKILL.md new file mode 100644 index 000000000..c4311061e --- /dev/null +++ b/windsurf-harnett/skills/opendap/SKILL.md @@ -0,0 +1,378 @@ +--- +name: opendap +description: Understanding OPeNDAP (Open-source Project for a Network Data Access Protocol) for accessing remote scientific data via HTTP, including DAP2/DAP4 protocols, constraint expressions, data models, and client integration. Use when working with OPeNDAP URLs, writing data access code, or integrating with NetCDF. +metadata: + author: opendap-documentation + version: "1.0" + date: "2026-01-19" +--- + +# OPeNDAP Skill + +This skill provides comprehensive knowledge of OPeNDAP to help you access, serve, and work with remote scientific data effectively. + +## Overview + +OPeNDAP (Open-source Project for a Network Data Access Protocol) provides a way for researchers to access scientific data anywhere on the Internet from a wide variety of programs. It uses a client/server architecture built on HTTP and provides flexible data subsetting through constraint expressions. + +**Key Features**: +- Network-transparent data access via URLs +- Data subsetting at the server (reduces bandwidth) +- Format-independent data model +- Compatible with NetCDF, HDF5, and other formats +- Supports gridded data, sequences, and complex structures + +## Core Concepts + +### 1. Client/Server Architecture + +OPeNDAP uses a web-based client/server model similar to the World Wide Web: + +- **Server (Hyrax)**: Translates data from storage format to DAP format for transmission +- **Client**: Requests data via URLs and translates DAP format to local API format +- **Protocol**: HTTP-based Data Access Protocol (DAP) + +**Data Flow**: +``` +User Program → OPeNDAP Client Library → HTTP Request → OPeNDAP Server + ↓ +User Program ← Translated Data ← DAP Response ← Read Local Files +``` + +### 2. OPeNDAP URLs + +An OPeNDAP URL identifies a dataset and optionally includes a constraint expression: + +**Basic URL Structure**: +``` +http://server.domain/path/to/dataset.nc +``` + +**URL with Constraint Expression**: +``` +http://server.domain/path/to/dataset.nc?variable[start:stop]&selection_clause +``` + +**URL Suffixes** (Service Endpoints): +- `.dds` - Dataset Descriptor Structure (DAP2 - data shape) +- `.das` - Data Attribute Structure (DAP2 - metadata) +- `.dmr.xml` - Dataset Metadata Response (DAP4 - combined structure) +- `.dods` - Binary data (DAP2) +- `.dap` - Binary data (DAP4) +- `.ascii` - ASCII representation of data +- `.html` - Web form interface +- `.info` - Combined DDS + DAS in HTML + +### 3. Data Access Protocol (DAP) + +**DAP2** (Older, widely supported): +- Separate DDS and DAS responses +- Binary data in .dods format +- Simpler data model + +**DAP4** (Newer, enhanced): +- Unified DMR (Dataset Metadata Response) in XML +- Enhanced data model with groups +- Better support for complex types +- Improved performance + +## OPeNDAP Data Model + +### Base Types + +- **Byte, Int16, Int32, Int64** - Integer types +- **UInt16, UInt32, UInt64** - Unsigned integers +- **Float32, Float64** - Floating-point numbers +- **String** - Character strings +- **URL** - Uniform Resource Locators + +### Constructor Types + +- **Array** - Multi-dimensional arrays with indexing +- **Structure** - Collection of related variables +- **Sequence** - Ordered collection of instances (like database rows) +- **Grid** - Array with coordinate map vectors + +**Example Grid**: +``` +Grid { + Array: + Int16 sst[time=1857][lat=89][lon=180]; + Maps: + Float64 time[time=1857]; + Float64 lat[lat=89]; + Float64 lon[lon=180]; +} sst; +``` + +## Constraint Expressions + +Constraint expressions allow you to subset data on the server before transmission. + +### Syntax + +``` +URL?projection&selection +``` + +- **Projection**: Comma-separated list of variables to return +- **Selection**: Boolean expressions to filter data (prefixed with &) + +### Array Subsetting + +**Single element**: +``` +?variable[index] +?sst[0][10][20] +``` + +**Range (start:stop)**: +``` +?variable[start:stop] +?sst[0:10][20:30][40:50] +``` + +**Stride (start:stride:stop)**: +``` +?variable[start:stride:stop] +?sst[0:2:100] # Every 2nd element from 0 to 100 +``` + +### Selection Clauses + +**Comparison operators**: `<`, `>`, `<=`, `>=`, `=`, `!=` + +**Examples**: +``` +?station&station.temp>20.0 +?station&station.lat>0.0&station.lon<-60.0 +?station&station.month={4,5,6,7} # List for OR +``` + +### Server Functions + +Hyrax servers support functions for advanced operations: + +**geogrid()** - Subset by geographic coordinates: +``` +?geogrid(sst, 62, 206, 56, 210, "19722 + +int ncid, varid; +char *url = "http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz"; + +// Open remote dataset +nc_open(url, NC_NOWRITE, &ncid); + +// Access variables normally +nc_inq_varid(ncid, "sst", &varid); + +// Read data with subsetting +size_t start[] = {0, 0, 0}; +size_t count[] = {1, 10, 10}; +float data[10][10]; +nc_get_vara_float(ncid, varid, start, count, &data[0][0]); + +nc_close(ncid); +``` + +### URL with Constraint Expression + +You can include constraint expressions in the URL: + +```c +char *url = "http://server.org/data.nc?sst[0:10][20:30][40:50]"; +nc_open(url, NC_NOWRITE, &ncid); +``` + +### Fortran Programs + +```fortran +program read_opendap + use netcdf + implicit none + + integer :: ncid, varid, status + character(len=256) :: url + real :: data(10, 10) + + url = "http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz" + + status = nf90_open(url, NF90_NOWRITE, ncid) + status = nf90_inq_varid(ncid, "sst", varid) + status = nf90_get_var(ncid, varid, data, & + start=[1,1,1], count=[10,10,1]) + status = nf90_close(ncid) + +end program read_opendap +``` + +## Common Workflows + +### 1. Exploring a Dataset + +**Step 1**: Get the DMR/DDS to see structure: +``` +http://server.org/data.nc.dmr.xml +http://server.org/data.nc.dds (DAP2) +``` + +**Step 2**: Get attributes: +``` +http://server.org/data.nc.das (DAP2) +``` + +**Step 3**: Use .info for combined view: +``` +http://server.org/data.nc.info +``` + +### 2. Subsetting Data + +**Step 1**: Identify variable and dimensions from DMR/DDS + +**Step 2**: Construct constraint expression: +``` +?variable[time_start:time_end][lat_start:lat_end][lon_start:lon_end] +``` + +**Step 3**: Test with .ascii to verify: +``` +http://server.org/data.nc.ascii?sst[0:1][10:20][30:40] +``` + +**Step 4**: Use in your program with full URL + +### 3. Working with Sequences + +Sequences are like database tables with rows of data: + +**Get specific fields**: +``` +?sequence.field1,sequence.field2 +``` + +**Filter rows**: +``` +?sequence.field1,sequence.field2&sequence.field1>100 +``` + +**Multiple conditions**: +``` +?sequence&sequence.temp>20&sequence.depth<100 +``` + +## Client Tools + +### Matlab + +Matlab 2012a+ has built-in OPeNDAP support via NetCDF interface: + +```matlab +url = 'http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz'; +ncid = netcdf.open(url); +data = netcdf.getVar(ncid, varid); +netcdf.close(ncid); +``` + +### Python + +Using netCDF4-python or xarray: + +```python +import netCDF4 +url = "http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz" +ds = netCDF4.Dataset(url) +sst = ds.variables['sst'][0:10, 20:30, 40:50] +``` + +### Command-line Tools + +**ncdump** (with OPeNDAP-enabled NetCDF): +```bash +ncdump -h "http://server.org/data.nc" +ncdump -v sst "http://server.org/data.nc?sst[0:10][20:30]" +``` + +## Troubleshooting + +### Common Issues + +**1. URL not recognized**: +- Ensure NetCDF library is compiled with DAP support +- Check URL syntax (http:// or https://) + +**2. Constraint expression errors**: +- Verify variable names match DDS/DMR exactly +- Check array bounds (0-indexed) +- Ensure proper quoting in shell commands + +**3. Performance issues**: +- Use constraint expressions to reduce data transfer +- Request only needed variables +- Consider server-side functions for processing + +**4. Authentication**: +- Some servers require credentials +- Use .netrc file or URL-embedded credentials +- Check server documentation for auth methods + +## Quick Reference + +### Essential URL Patterns + +``` +# Get metadata +http://server/dataset.nc.dmr.xml (DAP4) +http://server/dataset.nc.dds (DAP2) +http://server/dataset.nc.das (DAP2) + +# Get data +http://server/dataset.nc?var[0:10] +http://server/dataset.nc.ascii?var[0:10] + +# Multiple variables +http://server/dataset.nc?var1,var2,var3 + +# With selection +http://server/dataset.nc?var&var>100 +``` + +### Constraint Expression Operators + +- Array: `[start:stop]`, `[start:stride:stop]` +- Comparison: `<`, `>`, `<=`, `>=`, `=`, `!=` +- String match: `~=` (regex) +- Lists: `{val1,val2,val3}` (OR operation) +- Structure fields: `structure.field` + +## Additional Resources + +See [references/PROTOCOL.md](references/PROTOCOL.md) for DAP2 and DAP4 protocol details. + +See [references/CONSTRAINTS.md](references/CONSTRAINTS.md) for comprehensive constraint expression examples. + +See [references/DATA-MODEL.md](references/DATA-MODEL.md) for complete data type documentation. + +See [references/CLIENT-USAGE.md](references/CLIENT-USAGE.md) for client integration examples. diff --git a/windsurf-harnett/skills/opendap/references/CLIENT-USAGE.md b/windsurf-harnett/skills/opendap/references/CLIENT-USAGE.md new file mode 100644 index 000000000..bccf24540 --- /dev/null +++ b/windsurf-harnett/skills/opendap/references/CLIENT-USAGE.md @@ -0,0 +1,339 @@ +# OPeNDAP Client Usage + +This document provides examples of using OPeNDAP with various client libraries and tools. + +## C Programs with NetCDF + +### Basic Data Access + +```c +#include +#include + +int main() { + int ncid, varid, status; + size_t start[3] = {0, 0, 0}; + size_t count[3] = {1, 10, 10}; + float data[10][10]; + + char *url = "http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz"; + + status = nc_open(url, NC_NOWRITE, &ncid); + if (status != NC_NOERR) { + fprintf(stderr, "Error opening URL: %s\n", nc_strerror(status)); + return 1; + } + + status = nc_inq_varid(ncid, "sst", &varid); + status = nc_get_vara_float(ncid, varid, start, count, &data[0][0]); + + printf("Data at [0][5][5]: %f\n", data[5][5]); + + nc_close(ncid); + return 0; +} +``` + +### Using Constraint Expressions + +```c +char *url = "http://server.org/data.nc?sst[0:10][20:30][40:50]"; +nc_open(url, NC_NOWRITE, &ncid); +``` + +### Reading Metadata + +```c +int ndims, nvars, ngatts, unlimdimid; +char varname[NC_MAX_NAME+1]; + +nc_inq(ncid, &ndims, &nvars, &ngatts, &unlimdimid); + +for (int i = 0; i < nvars; i++) { + nc_inq_varname(ncid, i, varname); + printf("Variable %d: %s\n", i, varname); +} +``` + +## Fortran Programs + +### Basic Example + +```fortran +program read_opendap + use netcdf + implicit none + + integer :: ncid, varid, status + integer :: start(3), count(3) + real :: data(10, 10, 1) + character(len=256) :: url + + url = "http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz" + + status = nf90_open(url, NF90_NOWRITE, ncid) + if (status /= NF90_NOERR) then + print *, "Error: ", trim(nf90_strerror(status)) + stop + end if + + status = nf90_inq_varid(ncid, "sst", varid) + + start = [1, 1, 1] + count = [10, 10, 1] + status = nf90_get_var(ncid, varid, data, start=start, count=count) + + print *, "Sample value: ", data(5, 5, 1) + + status = nf90_close(ncid) + +end program read_opendap +``` + +### Time Series Extraction + +```fortran +program time_series + use netcdf + implicit none + + integer :: ncid, varid, status, nt + real, allocatable :: temp(:) + character(len=256) :: url + + url = "http://server.org/data.nc?temp[0:1000][45][90]" + + status = nf90_open(url, NF90_NOWRITE, ncid) + status = nf90_inq_varid(ncid, "temp", varid) + status = nf90_inq_dimlen(ncid, 1, nt) + + allocate(temp(nt)) + status = nf90_get_var(ncid, varid, temp) + + print *, "Time series length: ", nt + print *, "Mean: ", sum(temp)/nt + + deallocate(temp) + status = nf90_close(ncid) + +end program time_series +``` + +## Python Examples + +### Using netCDF4-python + +```python +import netCDF4 as nc + +url = "http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz" +dataset = nc.Dataset(url) + +print("Variables:", dataset.variables.keys()) + +sst = dataset.variables['sst'] +print("Shape:", sst.shape) +print("Units:", sst.units) + +data = sst[0:10, 20:30, 40:50] +print("Subset shape:", data.shape) + +dataset.close() +``` + +### Using xarray + +```python +import xarray as xr + +url = "http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz" +ds = xr.open_dataset(url) + +print(ds) + +sst_subset = ds.sst.isel(time=slice(0, 10), lat=slice(20, 30), lon=slice(40, 50)) +mean_sst = sst_subset.mean() + +print(f"Mean SST: {mean_sst.values}") +``` + +### With Constraint Expressions + +```python +url_with_constraint = "http://server.org/data.nc?sst[0:100][0:50][0:80]" +ds = nc.Dataset(url_with_constraint) +``` + +## Matlab Examples + +### Basic Access + +```matlab +url = 'http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz'; +ncid = netcdf.open(url); + +[numdims, numvars, numglobalatts, unlimdimid] = netcdf.inq(ncid); +fprintf('Number of variables: %d\n', numvars); + +varid = netcdf.inqVarID(ncid, 'sst'); +data = netcdf.getVar(ncid, varid, [0,0,0], [10,10,1]); + +fprintf('Sample value: %f\n', data(5,5)); + +netcdf.close(ncid); +``` + +### Subsetting + +```matlab +url = 'http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz'; +ncid = netcdf.open(url); +varid = netcdf.inqVarID(ncid, 'sst'); + +start = [2, 7, 0]; +count = [10, 8, 1]; +stride = [1, 1, 1]; +data = netcdf.getVar(ncid, varid, start, count, stride); + +imagesc(data'); +colorbar; +title('SST Subset'); +``` + +## Command-Line Tools + +### ncdump + +```bash +# View header +ncdump -h "http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz" + +# Dump specific variable +ncdump -v sst "http://server.org/data.nc?sst[0:10][20:30]" + +# ASCII output +ncdump "http://server.org/data.nc" > output.txt +``` + +### curl + +```bash +# Get DDS +curl "http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz.dds" + +# Get DAS +curl "http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz.das" + +# Get ASCII data +curl "http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz.ascii?sst[0][0:5][0:5]" +``` + +## Error Handling + +### C Error Handling + +```c +int status; +char *url = "http://server.org/data.nc"; + +status = nc_open(url, NC_NOWRITE, &ncid); +if (status != NC_NOERR) { + fprintf(stderr, "NetCDF error: %s\n", nc_strerror(status)); + if (status == NC_ENOTNC) { + fprintf(stderr, "Not a valid NetCDF/OPeNDAP URL\n"); + } + return 1; +} +``` + +### Python Error Handling + +```python +try: + ds = nc.Dataset(url) + data = ds.variables['sst'][:] +except RuntimeError as e: + print(f"OPeNDAP error: {e}") +except KeyError as e: + print(f"Variable not found: {e}") +finally: + if 'ds' in locals(): + ds.close() +``` + +## Performance Tips + +### Minimize Requests + +```c +// Bad - multiple small requests +for (int i = 0; i < 100; i++) { + nc_get_vara_float(ncid, varid, &i, &one, &value); +} + +// Good - single larger request +nc_get_vara_float(ncid, varid, start, count, data); +``` + +### Use Constraint Expressions + +```python +# Bad - download everything then subset +ds = nc.Dataset("http://server.org/data.nc") +subset = ds.variables['sst'][0:10, 20:30, 40:50] + +# Good - subset at server +url = "http://server.org/data.nc?sst[0:10][20:30][40:50]" +ds = nc.Dataset(url) +subset = ds.variables['sst'][:] +``` + +### Cache Metadata + +```python +# Cache dataset structure +ds = nc.Dataset(url) +var_names = list(ds.variables.keys()) +dims = {name: len(ds.dimensions[name]) for name in ds.dimensions} + +# Reuse cached info for multiple accesses +for var_name in var_names: + data = ds.variables[var_name][:] +``` + +## Authentication + +### Using .netrc + +Create `~/.netrc`: +``` +machine server.org +login username +password mypassword +``` + +Set permissions: +```bash +chmod 600 ~/.netrc +``` + +### URL-Embedded Credentials + +```c +char *url = "http://username:password@server.org/data.nc"; +``` + +Note: Not recommended for security reasons. + +## Best Practices + +1. **Check metadata first** before requesting data +2. **Use constraint expressions** to minimize data transfer +3. **Handle errors gracefully** with proper error checking +4. **Cache when possible** to reduce repeated requests +5. **Close connections** when done +6. **Test URLs** with .ascii or .dds before using in code +7. **Use appropriate data types** matching server types +8. **Consider time zones** for temporal data +9. **Validate data** after retrieval (check for fill values) +10. **Document URLs** and constraint expressions in code diff --git a/windsurf-harnett/skills/opendap/references/CONSTRAINTS.md b/windsurf-harnett/skills/opendap/references/CONSTRAINTS.md new file mode 100644 index 000000000..f134b004d --- /dev/null +++ b/windsurf-harnett/skills/opendap/references/CONSTRAINTS.md @@ -0,0 +1,486 @@ +# OPeNDAP Constraint Expressions + +This document provides comprehensive examples and patterns for OPeNDAP constraint expressions. + +## Constraint Expression Basics + +A constraint expression has two parts: + +``` +URL?projection&selection +``` + +- **Projection**: What data to return (variables, array subsets) +- **Selection**: How to filter the data (boolean conditions) + +Both parts are optional. Either or both can be used. + +## Projection Expressions + +### Selecting Variables + +**Single variable**: +``` +?temperature +``` + +**Multiple variables**: +``` +?temperature,salinity,pressure +``` + +**Structure fields**: +``` +?station.latitude,station.longitude,station.time +``` + +**Nested structures**: +``` +?cruise.station.cast.temperature +``` + +### Array Subsetting + +**Single element**: +``` +?sst[0][10][20] +``` + +**Range (start:stop)**: +``` +?sst[0:10][20:30][40:50] +``` + +**Stride (start:stride:stop)**: +``` +?sst[0:2:100][0:5:50][0:10:180] +# Every 2nd time, every 5th lat, every 10th lon +``` + +**Open-ended ranges**: +``` +?sst[10:] # From index 10 to end +?sst[:100] # From start to index 100 +?sst[:] # Entire dimension +``` + +### Grid Subsetting + +When subsetting a Grid, include coordinate variables: + +**Without coordinates** (just the array): +``` +?sst[0:10][20:30][40:50] +``` + +**With coordinates**: +``` +?time[0:10],lat[20:30],lon[40:50],sst[0:10][20:30][40:50] +``` + +**Using geogrid() function** (Hyrax servers): +``` +?geogrid(sst, north_lat, west_lon, south_lat, east_lon) +?geogrid(sst, 62, 206, 56, 210) +``` + +## Selection Expressions + +### Comparison Operators + +**Numeric comparisons**: +``` +?station&station.temperature>20.0 +?station&station.depth<100 +?station&station.salinity>=34.5 +?station&station.pressure<=1000 +?station&station.id=12345 +?station&station.quality!=0 +``` + +**String comparisons**: +``` +?station&station.name="Station_A" +?station&station.type!="reference" +``` + +**String pattern matching** (regex): +``` +?station&station.comment~=".*shark.*" +?station&station.location~="^North.*" +``` + +### Multiple Conditions + +**AND conditions** (multiple & clauses): +``` +?station&station.lat>0.0&station.lon<-60.0 +?station&station.temp>20&station.depth<50&station.salinity>34 +``` + +**OR conditions** (using lists): +``` +?station&station.month={4,5,6,7} +?station&station.type={"CTD","XBT","profiler"} +``` + +**Combining variables in lists**: +``` +?station&station.month={4,5,6,station.monsoon_month} +``` + +### Range Conditions + +**Value between bounds**: +``` +?station&station.temp>15&station.temp<25 +?data&1019722&data.time<19755 +``` + +## Sequence Operations + +Sequences are like database tables with rows of data. + +### Selecting Fields + +**Specific fields**: +``` +?sequence.field1,sequence.field2,sequence.field3 +``` + +**All fields with filter**: +``` +?sequence&sequence.temperature>20 +``` + +### Filtering Rows + +**Single condition**: +``` +?URI_GSO-Dock.Time,URI_GSO-Dock.Sea_Temp&URI_GSO-Dock.Time<35234.1 +``` + +**Multiple conditions**: +``` +?station.cast.press,station.cast.temp&station.cast.press>500.0 +?station.cast&station.cast.temp>22.0 +``` + +**Complex filters**: +``` +?station&station.lat>0.0&station.month={4,5,6,7} +``` + +## Server Functions + +### geogrid() - Geographic Subsetting + +**Syntax**: +``` +geogrid(variable, top, left, bottom, right, [other_expressions]) +``` + +**Example**: +``` +?geogrid(sst, 62, 206, 56, 210, "1972219722 +``` + +**Multiple variables with conditions**: +``` +?lat,lon,temp,salinity&temp>20&salinity>34 +``` + +### Working with Nested Structures + +**Nested sequence**: +``` +?cruise.station.cast.depth,cruise.station.cast.temp +``` + +**Filter on nested field**: +``` +?cruise.station&cruise.station.latitude>0 +``` + +**Multiple levels**: +``` +?cruise.station.cast&cruise.station.cast.temp>20 +``` + +### Sampling Patterns + +**Every Nth element**: +``` +?sst[0:10:1857][0:5:89][0:10:180] +``` + +**Sparse sampling**: +``` +?sst[::100][::10][::20] # Every 100th time, 10th lat, 20th lon +``` + +**Diagonal sampling** (if supported): +``` +?array[0:10][0:10] # 11x11 subset +``` + +## Common Use Cases + +### 1. Time Series at a Point + +``` +?time,sst[0:1857][44][90] +# All times, single lat/lon point +``` + +### 2. Spatial Subset at One Time + +``` +?lat[20:40],lon[100:140],sst[0][20:40][100:140] +# Single time, regional subset +``` + +### 3. Vertical Profile + +``` +?depth,temperature[0:500],salinity[0:500] +# Full depth profile +``` + +### 4. Quality-Filtered Data + +``` +?time,temp,salinity&quality_flag=1 +# Only high-quality data +``` + +### 5. Regional and Temporal Subset + +``` +?geogrid(sst, 45, -130, 30, -110, "1990025&wind_speed<5 +# Warm, calm conditions +``` + +## Pattern Matching + +### Regular Expression Syntax + +**Wildcards**: +- `.` - Any single character +- `.*` - Zero or more characters +- `.+` - One or more characters +- `.?` - Zero or one character + +**Anchors**: +- `^` - Start of string +- `$` - End of string + +**Character classes**: +- `[abc]` - Match a, b, or c +- `[0-9]` - Match any digit +- `[^0-9]` - Match any non-digit + +**Examples**: +``` +?station&station.comment~=".*shark.*" # Contains "shark" +?station&station.name~="^Station_[0-9]+$" # Station_123 format +?data&data.type~="CTD|XBT|profiler" # Multiple types +``` + +## Error Handling + +### Common Constraint Expression Errors + +**Invalid variable name**: +``` +Error: Variable 'sst_temp' not found +Fix: Check DDS/DMR for correct name +``` + +**Index out of bounds**: +``` +Error: Array index [2000] exceeds dimension size [1857] +Fix: Verify dimension sizes in DDS/DMR +``` + +**Syntax error**: +``` +Error: Expected ']' but found ',' +Fix: Check bracket matching and syntax +``` + +**Type mismatch**: +``` +Error: Cannot compare String with Float64 +Fix: Use appropriate operators for data type +``` + +### Testing Constraint Expressions + +**Use .ascii for debugging**: +``` +http://server/data.nc.ascii?sst[0:1][0:5][0:5] +``` + +**Check metadata first**: +``` +http://server/data.nc.dmr.xml +http://server/data.nc.dds +``` + +**Test incrementally**: +1. Start with simple projection: `?variable` +2. Add subsetting: `?variable[0:10]` +3. Add selection: `?variable[0:10]&variable>100` + +## Performance Tips + +### Minimize Data Transfer + +**Request only needed variables**: +``` +?temp,salinity # Not ?* +``` + +**Use appropriate stride**: +``` +?sst[0:10:1857] # Every 10th instead of all +``` + +**Subset at server**: +``` +?sst[0:100][20:40][50:80] # Not full array +``` + +### Leverage Server Functions + +**Process at server**: +``` +?linear_scale(geogrid(sst, 45, -130, 30, -110)) +``` + +**Combine operations**: +``` +?mean(sst[0:100][20:40][50:80]) # If server supports +``` + +### Cache Metadata + +**Reuse DDS/DAS/DMR**: +- Cache structure information +- Avoid repeated metadata requests +- Use cached info for constraint construction + +## Examples by Data Type + +### Gridded Data (Arrays/Grids) + +``` +# Single point +?sst[100][44][90] + +# Regional subset +?sst[0:100][20:40][80:120] + +# Time series at point +?time,sst[0:1857][44][90] + +# Spatial map at time +?lat,lon,sst[100][0:89][0:180] +``` + +### Station Data (Sequences) + +``` +# All stations +?station + +# Specific fields +?station.id,station.lat,station.lon,station.time + +# Filtered stations +?station&station.lat>0&station.lon<-60 + +# Quality filtered +?station.temp,station.salinity&station.quality=1 +``` + +### Profile Data (Nested Sequences) + +``` +# All profiles +?cruise.station.cast + +# Specific depths +?cruise.station.cast.depth,cruise.station.cast.temp&cruise.station.cast.depth<100 + +# Filtered by location +?cruise.station.cast&cruise.station.latitude>30 +``` + +## Best Practices + +1. **Always check metadata first** - Use .dds/.dmr.xml to understand structure +2. **Test with .ascii** - Verify constraint expressions before using in code +3. **Use server functions** - Leverage geogrid(), linear_scale(), etc. +4. **Minimize data transfer** - Request only what you need +5. **Handle errors gracefully** - Check HTTP status codes and error messages +6. **Cache when possible** - Reuse metadata and frequently accessed subsets +7. **Document constraints** - Complex expressions can be hard to understand later diff --git a/windsurf-harnett/skills/opendap/references/DATA-MODEL.md b/windsurf-harnett/skills/opendap/references/DATA-MODEL.md new file mode 100644 index 000000000..2e4471cd0 --- /dev/null +++ b/windsurf-harnett/skills/opendap/references/DATA-MODEL.md @@ -0,0 +1,556 @@ +# OPeNDAP Data Model + +This document provides comprehensive documentation of the OPeNDAP data model and type system. + +## Overview + +The OPeNDAP data model is designed to be general enough to represent data from various storage formats (NetCDF, HDF5, relational databases, etc.) while being specific enough to preserve the structure and relationships in the data. + +## Base Types + +Base types represent atomic data values. + +### Numeric Types + +**Byte** (8-bit signed integer): +``` +Byte temperature; +Range: -128 to 127 +``` + +**Int16** (16-bit signed integer): +``` +Int16 elevation; +Range: -32,768 to 32,767 +``` + +**Int32** (32-bit signed integer): +``` +Int32 station_id; +Range: -2,147,483,648 to 2,147,483,647 +``` + +**Int64** (64-bit signed integer, DAP4 only): +``` +Int64 timestamp; +Range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 +``` + +**UInt16** (16-bit unsigned integer): +``` +UInt16 count; +Range: 0 to 65,535 +``` + +**UInt32** (32-bit unsigned integer): +``` +UInt32 pixel_value; +Range: 0 to 4,294,967,295 +``` + +**UInt64** (64-bit unsigned integer, DAP4 only): +``` +UInt64 file_size; +Range: 0 to 18,446,744,073,709,551,615 +``` + +**Float32** (32-bit floating point): +``` +Float32 temperature; +IEEE 754 single precision +``` + +**Float64** (64-bit floating point): +``` +Float64 latitude; +IEEE 754 double precision +``` + +### String Types + +**String** (variable-length character string): +``` +String station_name; +String comment; +``` + +**URL** (Uniform Resource Locator): +``` +URL data_source; +``` + +## Constructor Types + +Constructor types build complex data structures from base types and other constructors. + +### Array + +Multi-dimensional arrays of any base or constructor type. + +**Syntax**: +``` +Type name[dim1][dim2]...[dimN]; +``` + +**Examples**: +``` +Float32 temperature[time=100][lat=50][lon=80]; +Int16 elevation[y=1000][x=1000]; +String station_names[stations=25]; +``` + +**Characteristics**: +- Fixed dimensions at creation +- Homogeneous (all elements same type) +- Zero-indexed +- Rectangular (not ragged) + +**Subsetting**: +``` +temperature[0:10][20:30][40:50] # Hyperslab +temperature[5][25][60] # Single element +temperature[0:2:100] # With stride +``` + +### Structure + +Named collection of variables (like a C struct). + +**Syntax**: +``` +Structure { + Type1 field1; + Type2 field2; + ... +} name; +``` + +**Example**: +``` +Structure { + Float64 latitude; + Float64 longitude; + Int32 elevation; + String name; +} station; +``` + +**Characteristics**: +- Heterogeneous (different types) +- Named fields +- Accessed via dot notation: `station.latitude` + +**Use Cases**: +- Grouping related metadata +- Station information +- Coordinate pairs +- Complex data records + +### Sequence + +Ordered collection of instances (like database rows). + +**Syntax**: +``` +Sequence { + Type1 field1; + Type2 field2; + ... +} name; +``` + +**Example**: +``` +Sequence { + Float64 time; + Float64 depth; + Float32 temperature; + Float32 salinity; +} cast; +``` + +**Characteristics**: +- Variable length (number of instances unknown) +- Each instance is a Structure +- Can be filtered with selection expressions +- Accessed one instance at a time + +**Nested Sequences**: +``` +Sequence { + Int32 station_id; + Float64 latitude; + Float64 longitude; + Sequence { + Float64 depth; + Float32 temperature; + Float32 salinity; + } measurements; +} station; +``` + +**Filtering**: +``` +?cast&cast.temperature>20 +?cast.depth,cast.temperature&cast.depth<100 +``` + +### Grid + +Array with coordinate map vectors (georeferenced data). + +**Syntax**: +``` +Grid { + Array: + Type array_name[dim1][dim2]...[dimN]; + Maps: + Type1 map1[dim1]; + Type2 map2[dim2]; + ... +} name; +``` + +**Example**: +``` +Grid { + Array: + Int16 sst[time=1857][lat=89][lon=180]; + Maps: + Float64 time[time=1857]; + Float64 lat[lat=89]; + Float64 lon[lon=180]; +} sst; +``` + +**Characteristics**: +- Combines array with coordinate variables +- Maps provide independent variable values +- Common for geospatial data +- Subsetting returns both array and corresponding maps + +**Use Cases**: +- Gridded climate data +- Satellite imagery +- Model output +- Any regularly or irregularly spaced data + +## DAP4 Extensions + +DAP4 adds several enhancements to the data model. + +### Groups + +Hierarchical organization of variables (like HDF5 groups). + +**Example**: +``` +Group { + Float64 time[time=100]; + + Group heights { + Float32 delta_time[time=100]; + Float64 lat_ph[time=100]; + Float64 lon_ph[time=100]; + } + + Group quality { + Int32 qa_flag[time=100]; + } +} gt3r; +``` + +**Access**: +``` +?/gt3r/heights/lat_ph +?/gt3r/quality/qa_flag +``` + +### Opaque + +Binary data of unknown structure. + +**Syntax**: +``` +Opaque name; +``` + +**Use Cases**: +- Embedded images +- Compressed data +- Proprietary formats +- Binary metadata + +### Enumeration + +Named integer constants (like C enum). + +**Syntax**: +``` +Enum quality_flag { + good = 0, + questionable = 1, + bad = 2, + missing = 3 +}; +``` + +**Usage**: +``` +quality_flag qa[time=100]; +``` + +## Data Model Translation + +### From NetCDF-3 to OPeNDAP + +**NetCDF-3 Dimensions** → **OPeNDAP Array dimensions**: +``` +NetCDF: float temp(time, lat, lon); +OPeNDAP: Float32 temp[time][lat][lon]; +``` + +**NetCDF-3 Variables** → **OPeNDAP Grid** (if coordinate variables exist): +``` +NetCDF: + float sst(time, lat, lon); + double time(time); + float lat(lat); + float lon(lon); + +OPeNDAP: + Grid { + Array: Float32 sst[time][lat][lon]; + Maps: Float64 time[time]; + Float32 lat[lat]; + Float32 lon[lon]; + } sst; +``` + +**NetCDF-3 Attributes** → **OPeNDAP DAS**: +``` +NetCDF: sst:units = "degC"; +OPeNDAP DAS: + sst { + String units "degC"; + } +``` + +### From HDF5 to OPeNDAP + +**HDF5 Groups** → **DAP4 Groups**: +``` +HDF5: /group1/subgroup/dataset +DAP4: /group1/subgroup/dataset +``` + +**HDF5 Datasets** → **OPeNDAP Arrays**: +``` +HDF5: Dataset "temperature" [100][50][80] +DAP4: Float32 temperature[100][50][80]; +``` + +**HDF5 Compound Types** → **OPeNDAP Structures**: +``` +HDF5 Compound: + { + "lat": H5T_NATIVE_DOUBLE, + "lon": H5T_NATIVE_DOUBLE, + "elev": H5T_NATIVE_INT + } + +OPeNDAP: + Structure { + Float64 lat; + Float64 lon; + Int32 elev; + } +``` + +### From Relational Database to OPeNDAP + +**Database Table** → **OPeNDAP Sequence**: +``` +SQL Table: + CREATE TABLE stations ( + id INT, + lat DOUBLE, + lon DOUBLE, + name VARCHAR(50) + ); + +OPeNDAP: + Sequence { + Int32 id; + Float64 lat; + Float64 lon; + String name; + } stations; +``` + +**SQL WHERE** → **OPeNDAP Selection**: +``` +SQL: SELECT * FROM stations WHERE lat > 0; +OPeNDAP: ?stations&stations.lat>0 +``` + +## Data Type Sizes + +| Type | Size (bytes) | Notes | +|------|--------------|-------| +| Byte | 1 | Signed | +| Int16 | 2 | Signed | +| Int32 | 4 | Signed | +| Int64 | 8 | Signed, DAP4 only | +| UInt16 | 2 | Unsigned | +| UInt32 | 4 | Unsigned | +| UInt64 | 8 | Unsigned, DAP4 only | +| Float32 | 4 | IEEE 754 | +| Float64 | 8 | IEEE 754 | +| String | Variable | Null-terminated | +| URL | Variable | Null-terminated | + +## Attributes + +Attributes provide metadata about variables and datasets. + +### Attribute Types + +All base types can be used as attributes: +``` +String long_name "Sea Surface Temperature"; +String units "degrees_C"; +Float32 valid_min -5.0; +Float32 valid_max 45.0; +Int32 missing_value -9999; +``` + +### Attribute Containers + +**Variable attributes**: +``` +sst { + String long_name "Sea Surface Temperature"; + String units "degC"; + Float32 scale_factor 0.01; +} +``` + +**Global attributes**: +``` +NC_GLOBAL { + String title "COADS 1-degree Enhanced"; + String institution "NOAA/PMEL"; + String history "Created 2020-01-15"; +} +``` + +### Standard Attributes + +**CF Conventions**: +- `units` - Physical units +- `long_name` - Descriptive name +- `standard_name` - CF standard name +- `valid_min`, `valid_max` - Valid range +- `_FillValue` - Missing data indicator +- `scale_factor`, `add_offset` - Packing parameters + +## Complex Data Examples + +### Time Series Station Data + +``` +Dataset { + Sequence { + Int32 station_id; + Float64 latitude; + Float64 longitude; + String station_name; + Sequence { + Float64 time; + Float32 temperature; + Float32 salinity; + Float32 pressure; + Int32 quality_flag; + } measurements; + } stations; +} ocean_observations; +``` + +### Satellite Imagery with Metadata + +``` +Dataset { + Structure { + String satellite_name; + String sensor_type; + Float64 acquisition_time; + Structure { + Float64 upper_left_lat; + Float64 upper_left_lon; + Float64 lower_right_lat; + Float64 lower_right_lon; + } bounds; + } metadata; + + Grid { + Array: + UInt16 radiance[line=1000][pixel=1000]; + Maps: + Float32 latitude[line=1000]; + Float32 longitude[pixel=1000]; + } image; +} satellite_scene; +``` + +### Climate Model Output + +``` +Dataset { + Grid { + Array: + Float32 temperature[time=365][level=50][lat=180][lon=360]; + Maps: + Float64 time[time=365]; + Float32 level[level=50]; + Float32 lat[lat=180]; + Float32 lon[lon=360]; + } air_temperature; + + Grid { + Array: + Float32 u_wind[time=365][level=50][lat=180][lon=360]; + Maps: + Float64 time[time=365]; + Float32 level[level=50]; + Float32 lat[lat=180]; + Float32 lon[lon=360]; + } eastward_wind; +} climate_model; +``` + +## Best Practices + +### Choosing Data Types + +1. **Use Grid for georeferenced data** with coordinate variables +2. **Use Sequence for variable-length data** (stations, profiles) +3. **Use Structure for related metadata** (location, time, etc.) +4. **Use appropriate numeric precision** (Float32 vs Float64) +5. **Use String for text data** (names, comments) + +### Data Organization + +1. **Group related variables** in Structures +2. **Use meaningful names** for variables and fields +3. **Include coordinate variables** for arrays +4. **Add comprehensive attributes** for metadata +5. **Follow conventions** (CF, COARDS, etc.) + +### Performance Considerations + +1. **Array layout matters** - row-major vs column-major +2. **Sequence access is sequential** - can't random access +3. **Grid subsetting is efficient** - server-side processing +4. **Structure access is fast** - all fields together +5. **String data is variable-length** - can impact performance diff --git a/windsurf-harnett/skills/opendap/references/PROTOCOL.md b/windsurf-harnett/skills/opendap/references/PROTOCOL.md new file mode 100644 index 000000000..c16dfd2ed --- /dev/null +++ b/windsurf-harnett/skills/opendap/references/PROTOCOL.md @@ -0,0 +1,345 @@ +# OPeNDAP Protocol Details + +This document provides detailed information about the Data Access Protocol (DAP) versions 2 and 4. + +## DAP2 Protocol + +DAP2 is the original OPeNDAP protocol, widely supported and stable. + +### DAP2 Responses + +**Dataset Descriptor Structure (DDS)**: +- Describes the "shape" of the data +- C-like syntax showing variables, dimensions, and types +- Accessed via `.dds` suffix + +Example DDS: +``` +Dataset { + Grid { + Array: + Int16 sst[time = 1857][lat = 89][lon = 180]; + Maps: + Float64 time[time = 1857]; + Float64 lat[lat = 89]; + Float64 lon[lon = 180]; + } sst; + Float64 time_bnds[time = 1857][nbnds = 2]; +} sst.mnmean.nc; +``` + +**Data Attribute Structure (DAS)**: +- Contains metadata about variables +- Includes units, descriptions, valid ranges, etc. +- Accessed via `.das` suffix + +Example DAS: +``` +Attributes { + sst { + String long_name "Monthly Means of Sea Surface Temperature"; + String units "degC"; + Float32 scale_factor 0.01; + Float32 add_offset 0.0; + Int16 missing_value 32767; + String valid_range "-500, 4500"; + } + NC_GLOBAL { + String title "COADS 1-degree Equatorial Enhanced"; + String history "Created by NOAA/PMEL"; + } +} +``` + +**Binary Data (.dods)**: +- XDR-encoded binary data +- Includes DDS followed by data values +- Most efficient for data transfer + +### DAP2 Data Types + +**Simple Types**: +- Byte, Int16, Int32 +- UInt16, UInt32 +- Float32, Float64 +- String, URL + +**Constructor Types**: +- Array - Multi-dimensional arrays +- Structure - Named collection of variables +- Sequence - Ordered list of structures +- Grid - Array with coordinate maps + +### DAP2 Constraint Expression Format + +**Projection** (what to return): +``` +?var1,var2,var3 +?structure.field1,structure.field2 +?array[start:stop] +``` + +**Selection** (filtering): +``` +?var&var>value +?sequence&sequence.field<100 +``` + +## DAP4 Protocol + +DAP4 is the enhanced protocol with improved features and performance. + +### DAP4 Responses + +**Dataset Metadata Response (DMR)**: +- Unified XML document combining structure and attributes +- Accessed via `.dmr.xml` suffix +- Supports groups (hierarchical organization) + +Example DMR: +```xml + + + + + + + + + + + Monthly Means of Sea Surface Temperature + + + degC + + + 0.01 + + + + + + + days since 1800-1-1 00:00:00 + + + +``` + +**Binary Data (.dap)**: +- More efficient encoding than DAP2 +- Chunked transfer for large datasets +- Better compression support + +### DAP4 Enhancements + +**Groups**: +- Hierarchical organization like HDF5 +- Namespace management +- Example: `/group1/subgroup/variable` + +**Enhanced Types**: +- Opaque - Binary blobs +- Enum - Enumerated types +- 64-bit integers (Int64, UInt64) + +**Improved Constraint Expressions**: +``` +?dap4.ce=/variable[0:10] +?dap4.ce=/group/variable&/group/variable>100 +``` + +### DAP4 Constraint Expression Format + +**Projection with namespace**: +``` +?dap4.ce=/sst +?dap4.ce=/gt3r/heights/delta_time,/gt3r/heights/lon_ph +``` + +**Filters**: +``` +?dap4.ce=/sst[0:100][0:50][0:80] +?dap4.ce=/sequence{field1,field2|field1>100} +``` + +## Protocol Comparison + +| Feature | DAP2 | DAP4 | +|---------|------|------| +| Metadata | Separate DDS/DAS | Unified DMR (XML) | +| Groups | No | Yes | +| 64-bit integers | No | Yes | +| Encoding | XDR | More efficient | +| Chunking | Limited | Full support | +| Constraint syntax | Simple | Enhanced | +| Adoption | Universal | Growing | + +## Choosing DAP Version + +**Use DAP2 when**: +- Maximum compatibility needed +- Working with older servers +- Simple data structures +- Established workflows + +**Use DAP4 when**: +- Working with grouped data (HDF5-like) +- Need 64-bit integer support +- Large dataset performance critical +- Server supports DAP4 + +## HTTP Details + +### Request Methods + +**GET requests** for all operations: +``` +GET /path/to/dataset.nc.dds HTTP/1.1 +Host: server.domain +``` + +### Response Headers + +``` +Content-Type: application/vnd.opendap.dds +Content-Type: application/vnd.opendap.das +Content-Type: application/vnd.opendap.data +Content-Type: text/xml (for DMR) +``` + +### Authentication + +**Basic HTTP Auth**: +``` +http://username:password@server.org/data.nc +``` + +**.netrc file** (recommended): +``` +machine server.org +login username +password mypassword +``` + +**OAuth/Bearer tokens** (server-dependent): +``` +Authorization: Bearer +``` + +## Error Handling + +### DAP2 Errors + +Errors returned as text with HTTP error codes: +``` +Error { + code = 404; + message = "Variable 'xyz' not found"; +}; +``` + +### DAP4 Errors + +XML error responses: +```xml + + Variable not found: xyz + Dataset: /data/file.nc + Check variable name spelling + +``` + +### Common HTTP Status Codes + +- **200 OK** - Success +- **400 Bad Request** - Invalid constraint expression +- **401 Unauthorized** - Authentication required +- **404 Not Found** - Dataset or variable not found +- **500 Internal Server Error** - Server-side error + +## Performance Considerations + +### Bandwidth Optimization + +1. **Use constraint expressions** to subset at server +2. **Request only needed variables** in projection +3. **Use appropriate stride** for sampling +4. **Leverage server functions** for processing + +### Caching + +Servers may cache: +- Metadata responses (DDS, DAS, DMR) +- Constraint expression results +- Aggregated datasets + +Clients should cache: +- Metadata for repeated access +- Frequently accessed subsets + +### Compression + +DAP4 supports: +- Chunked transfer encoding +- Gzip compression +- Server-side compression filters + +Request compressed responses: +``` +Accept-Encoding: gzip, deflate +``` + +## Server Capabilities + +### Version Detection + +Query server version: +``` +http://server.org/opendap/version +``` + +### Function Discovery + +List available server functions: +``` +http://server.org/data.nc?version() +``` + +Get function help: +``` +http://server.org/data.nc?function_name() +``` + +### Service Endpoints + +Standard endpoints: +- `.dds` - DAP2 structure +- `.das` - DAP2 attributes +- `.dods` - DAP2 binary data +- `.dmr.xml` - DAP4 metadata +- `.dap` - DAP4 binary data +- `.ascii` - ASCII representation +- `.html` - Web form +- `.info` - Combined info page +- `.ver` - Version information + +## Protocol Extensions + +### Server-Side Processing + +Some servers support: +- **Aggregation** - Combine multiple files +- **Subsetting** - Spatial/temporal subsetting +- **Transformation** - Unit conversion, reprojection +- **Statistics** - Mean, min, max calculations + +### Custom Functions + +Servers can implement custom functions: +``` +?custom_function(variable, param1, param2) +``` + +Check server documentation for available functions. diff --git a/windsurf-harnett/skills/pio/skill.md b/windsurf-harnett/skills/pio/skill.md new file mode 100644 index 000000000..05c8158cb --- /dev/null +++ b/windsurf-harnett/skills/pio/skill.md @@ -0,0 +1,591 @@ +# PIO (Parallel I/O) and Parallel I/O with NetCDF + +Understanding the ParallelIO (PIO) library for high-performance parallel I/O with netCDF on HPC systems. Use when writing parallel netCDF applications, optimizing I/O performance on many processors, or converting existing netCDF codes to use PIO. + +## Core Concepts + +### What is PIO? + +PIO is a high-level parallel I/O C and Fortran library for structured grid applications that provides: +- A netCDF-like API for familiar programming patterns +- Ability to designate a subset of processors for I/O operations +- Support for both synchronous and asynchronous I/O modes +- Integration with netCDF, parallel-netcdf (PnetCDF), and HDF5 libraries +- Distributed array decomposition and reassembly + +### Key Benefits + +- **Scalability**: Reduces I/O contention on HPC systems with many processors +- **Flexibility**: Choose between different I/O modes and strategies +- **Performance**: Optimizes data movement between compute and I/O tasks +- **Compatibility**: Works with existing netCDF code with minimal changes (via netCDF integration) + +## Architecture + +### Library Structure + +``` +Fortran Application Code + ↓ +PIO Fortran Library (wrapper) + ↓ +PIO C Library (core functionality) + ↓ + ┌────┴────┐ + ↓ ↓ +netCDF-C PnetCDF + ↓ + HDF5 + compression (optional) +``` + +### Dependencies + +**Required:** +- netCDF-C library (version 4.6.1+, built with MPI for parallel I/O) +- MPI-enabled C and Fortran compilers + +**Optional:** +- PnetCDF (version 1.9.0+) for parallel classic format I/O +- HDF5 (MPI-enabled) for netCDF-4 support +- Compression libraries (zlib, szip) + +## I/O Modes + +### Intracomm Mode + +All processors participate in computation, with a subset designated for I/O: +- I/O processors also do computational work +- Simpler setup for single computational units +- I/O tasks typically distributed across nodes to avoid bottlenecks + +**Initialization:** +```c +// C +int iosysid; +PIOc_Init_Intracomm(MPI_COMM_WORLD, niotasks, ioproc_stride, + ioproc_start, PIO_REARR_SUBSET, &iosysid); + +// With netCDF integration +nc_def_iosystem(MPI_COMM_WORLD, niotasks, ioproc_stride, + ioproc_start, PIO_REARR_SUBSET, &iosysid); +``` + +```fortran +! Fortran +call PIO_init(my_rank, MPI_COMM_WORLD, niotasks, num_aggregator, & + stride, PIO_rearr_subset, pio_iosystem, base=1) +``` + +### Async Mode + +Dedicated I/O processors service multiple computational units: +- I/O processors do NO computational work +- Multiple computation components can share I/O processors +- I/O processors wait in message loop for requests +- Better for complex multi-component applications + +**Initialization:** +```c +// C +int iosysid; +PIOc_init_async(world_comm, num_io_procs, io_proc_list, + component_count, num_procs_per_comp, + proc_list, io_comm, comp_comm, &iosysid); + +// With netCDF integration +nc_def_async(world_comm, num_io_procs, io_proc_list, + component_count, num_procs_per_comp, + proc_list, &iosysid); +``` + +## IOTYPEs (I/O Methods) + +PIO supports four I/O types, specified when creating/opening files: + +| IOTYPE | Library | Format | Parallel I/O | Compression | Notes | +|--------|---------|--------|--------------|-------------|-------| +| `PIO_IOTYPE_PNETCDF` | PnetCDF | Classic, 64-bit offset, CDF5 | Yes | No | Parallel classic formats only | +| `PIO_IOTYPE_NETCDF` | netCDF-C | Classic, 64-bit offset | No | No | Sequential, root I/O task only | +| `PIO_IOTYPE_NETCDF4C` | netCDF-C/HDF5 | NetCDF-4/HDF5 | No | Yes (level 1) | Sequential with compression | +| `PIO_IOTYPE_NETCDF4P` | netCDF-C/HDF5 | NetCDF-4/HDF5 | Yes | Yes | Parallel HDF5 I/O | + +**Selection criteria:** +- Use `PIO_IOTYPE_PNETCDF` for best parallel performance with classic formats +- Use `PIO_IOTYPE_NETCDF4P` for parallel I/O with compression +- Use `PIO_IOTYPE_NETCDF4C` when compression is critical and parallel I/O not needed +- Use `PIO_IOTYPE_NETCDF` for compatibility/debugging + +## Decompositions and Distributed Arrays + +### Concept + +A decomposition maps a global data array to local subarrays across processors: +- Each processor holds a portion of the global array +- Decomposition defines which global elements each processor owns +- One decomposition per netCDF data type +- Can be saved to/loaded from files + +### Creating a Decomposition + +**C API:** +```c +int ioid; +PIO_Offset *compdof; // 1-based array mapping +int elements_per_pe = DIM_LEN / ntasks; + +// Allocate and fill decomposition map +compdof = malloc(elements_per_pe * sizeof(PIO_Offset)); +for (int i = 0; i < elements_per_pe; i++) + compdof[i] = my_rank * elements_per_pe + i + 1; // 1-based! + +// Initialize decomposition +PIOc_InitDecomp(iosysid, PIO_INT, NDIM, dim_len, + elements_per_pe, compdof, &ioid, + NULL, NULL, NULL); +free(compdof); +``` + +**NetCDF Integration:** +```c +size_t *compdof; // 0-based for netCDF integration +compdof = malloc(elements_per_pe * sizeof(size_t)); +for (int i = 0; i < elements_per_pe; i++) + compdof[i] = my_rank * elements_per_pe + i; // 0-based! + +nc_def_decomp(iosysid, PIO_INT, NDIM2, &dimlen[1], + elements_per_pe, compdof, &ioid, 1, NULL, NULL); +``` + +**Fortran API:** +```fortran +type(io_desc_t) :: iodesc +integer, dimension(:) :: compdof + +! Define mapping (1-based in Fortran) +compdof = my_rank * elements_per_pe + (/ (i, i=1,elements_per_pe) /) + +call PIO_initdecomp(pio_iosystem, PIO_int, dims, compdof, iodesc) +``` + +### Freeing Decompositions + +Always free decomposition resources when done: +```c +// C +PIOc_freedecomp(iosysid, ioid); + +// NetCDF integration +nc_free_decomp(iosysid, ioid); +``` + +```fortran +! Fortran +call PIO_freedecomp(pio_iosystem, iodesc) +``` + +### Saving/Loading Decompositions + +```c +// Save decomposition to file +PIOc_write_nc_decomp(iosysid, filename, cmode, ioid, title, history, fortran_order); + +// Load decomposition from file +PIOc_read_nc_decomp(iosysid, filename, &ioid, comm, PIO_INT, title, history, &fortran_order); +``` + +## Rearrangers + +Rearrangers control how data moves between compute and I/O tasks: + +### BOX Rearranger (`PIO_REARR_BOX`) + +- Arranges data on I/O tasks to be contiguous on disk +- Requires all-to-all communication between compute and I/O tasks +- Better for underlying I/O library performance +- More communication overhead + +**Example:** Global array `0-19` over 5 compute tasks, 2 I/O tasks: +``` +Compute: {0,4,8,12} {16,1,5,9} {13,17,2,6} {10,14,18,3} {7,11,15,19} +I/O: {0,1,2,3,4,5,6,7,8,9} {10,11,12,13,14,15,16,17,18,19} +``` + +### SUBSET Rearranger (`PIO_REARR_SUBSET`) + +- Each I/O task associated with unique subset of compute tasks +- Each compute task sends to exactly one I/O task +- Less communication, but data may be fragmented on I/O tasks +- Usually scales better with task count + +**Example:** Same distribution: +``` +Compute: {0,4,8,12} {16,1,5,9} {13,17,2,6} {10,14,18,3} {7,11,15,19} +I/O: {0,1,4,5,8,9,12,16} {2,3,6,7,10,11,13,14,15,17,18,19} +``` + +**Selection:** SUBSET typically performs better at scale; BOX may be better for small task counts. + +## File Operations + +### Creating Files + +**PIO API:** +```c +// C +int ncid; +PIOc_createfile(iosysid, &ncid, &iotype, filename, PIO_CLOBBER); + +// Define metadata +PIOc_def_dim(ncid, "time", NC_UNLIMITED, &dimid); +PIOc_def_var(ncid, "temperature", PIO_FLOAT, ndims, dimids, &varid); +PIOc_put_att_text(ncid, varid, "units", strlen("K"), "K"); +PIOc_enddef(ncid); +``` + +```fortran +! Fortran +type(file_desc_t) :: pio_file +ret = PIO_createfile(pio_iosystem, pio_file, iotype, filename) +``` + +**NetCDF Integration:** +```c +// Use standard netCDF functions with NC_PIO flag +nc_create(filename, NC_PIO, &ncid); +nc_def_dim(ncid, "time", NC_UNLIMITED, &dimid); +nc_def_var(ncid, "temperature", NC_FLOAT, ndims, dimids, &varid); +``` + +### Opening Files + +```c +// PIO API +PIOc_openfile(iosysid, &ncid, &iotype, filename, PIO_NOWRITE); + +// NetCDF integration +nc_open(filename, NC_PIO, &ncid); +``` + +### Closing Files + +```c +// PIO API +PIOc_closefile(ncid); + +// NetCDF integration +nc_close(ncid); +``` + +## Reading and Writing Distributed Arrays + +### Writing Data + +**PIO API:** +```c +// Set record number (for unlimited dimension) +PIOc_setframe(ncid, varid, record_num); + +// Write distributed array +PIOc_write_darray(ncid, varid, ioid, arraylen, local_data, NULL); +``` + +```fortran +! Fortran +integer, dimension(:) :: local_data +call PIO_write_darray(pio_file, pio_var, iodesc, local_data, ret_val) +``` + +**NetCDF Integration:** +```c +// Write distributed array (record number is parameter) +nc_put_vard_int(ncid, varid, ioid, record_num, local_data); +nc_put_vard_float(ncid, varid, ioid, record_num, local_data); +``` + +### Reading Data + +**PIO API:** +```c +// Set record number +PIOc_setframe(ncid, varid, record_num); + +// Read distributed array +PIOc_read_darray(ncid, varid, ioid, arraylen, local_data); +``` + +```fortran +! Fortran +call PIO_read_darray(pio_file, pio_var, iodesc, local_data, ret_val) +``` + +**NetCDF Integration:** +```c +// Read distributed array +nc_get_vard_int(ncid, varid, ioid, record_num, local_data); +nc_get_vard_float(ncid, varid, ioid, record_num, local_data); +``` + +## Error Handling + +PIO provides three error handling modes: + +| Mode | Behavior | +|------|----------| +| `PIO_INTERNAL_ERROR` | Errors cause abort (default) | +| `PIO_BCAST_ERROR` | Error codes broadcast to all tasks | +| `PIO_RETURN_ERROR` | Errors returned to caller | + +**Setting error handler:** +```c +// Change default before initializing IOsystem +PIOc_set_iosystem_error_handling(PIO_DEFAULT, PIO_RETURN_ERROR, NULL); + +// Change for specific IOsystem +PIOc_set_iosystem_error_handling(iosysid, PIO_RETURN_ERROR, NULL); +``` + +```fortran +! Fortran +call PIO_seterrorhandling(pio_iosystem, PIO_RETURN_ERROR) +call PIO_seterrorhandling(PIO_DEFAULT, PIO_RETURN_ERROR) +``` + +## Finalization + +### Intracomm Mode + +All processors call finalize: +```c +// C +PIOc_finalize(iosysid); + +// NetCDF integration +nc_free_iosystem(iosysid); +``` + +```fortran +! Fortran +call PIO_finalize(pio_iosystem, ierr) +``` + +### Async Mode + +- Compute processors call finalize +- I/O processors receive message and free resources +- When all IOsystems freed, I/O processors exit message loop + +## NetCDF Integration + +### Building with Integration + +```bash +# Autotools +./configure --enable-netcdf-integration --enable-fortran + +# CMake +cmake -DPIO_ENABLE_NETCDF_INTEGRATION=On \ + -DNetCDF_C_PATH=/path/to/netcdf \ + -DPnetCDF_PATH=/path/to/pnetcdf .. +``` + +### Converting Existing NetCDF Code + +**Steps:** +1. Initialize IOsystem with `nc_def_iosystem()` or `nc_def_async()` +2. Use `NC_PIO` flag when creating/opening files +3. Define decomposition(s) with `nc_def_decomp()` +4. Replace `nc_put_vara_*()` with `nc_put_vard_*()` +5. Replace `nc_get_vara_*()` with `nc_get_vard_*()` +6. Free decompositions with `nc_free_decomp()` +7. Free IOsystem with `nc_free_iosystem()` + +**Key functions:** + +| NetCDF Integration | PIO API Equivalent | Purpose | +|-------------------|-------------------|---------| +| `nc_def_iosystem()` | `PIOc_Init_Intracomm()` | Initialize Intracomm mode | +| `nc_def_async()` | `PIOc_init_async()` | Initialize Async mode | +| `nc_def_decomp()` | `PIOc_init_decomp()` | Define decomposition | +| `nc_free_decomp()` | `PIOc_freedecomp()` | Free decomposition | +| `nc_put_vard_*()` | `PIOc_write_darray()` | Write distributed array | +| `nc_get_vard_*()` | `PIOc_read_darray()` | Read distributed array | +| `nc_free_iosystem()` | `PIOc_finalize()` | Free IOsystem | + +### Default IOsystem + +When using netCDF integration, the most recently created IOsystem becomes the default: +```c +// Get current default +int iosysid; +nc_get_iosystem(&iosysid); + +// Set default (for multiple IOsystems) +nc_set_iosystem(iosysid); +``` + +## Common Patterns + +### Basic PIO Workflow + +```c +// 1. Initialize MPI +MPI_Init(&argc, &argv); +MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); +MPI_Comm_size(MPI_COMM_WORLD, &ntasks); + +// 2. Initialize PIO IOsystem +PIOc_Init_Intracomm(MPI_COMM_WORLD, niotasks, stride, + base, PIO_REARR_SUBSET, &iosysid); + +// 3. Create decomposition +PIOc_InitDecomp(iosysid, PIO_FLOAT, ndims, dims, + maplen, compmap, &ioid, NULL, NULL, NULL); + +// 4. Create file and define metadata +PIOc_createfile(iosysid, &ncid, &iotype, filename, PIO_CLOBBER); +PIOc_def_dim(ncid, "x", xdim, &dimids[0]); +PIOc_def_var(ncid, "data", PIO_FLOAT, ndims, dimids, &varid); +PIOc_enddef(ncid); + +// 5. Write data +for (int rec = 0; rec < nrecs; rec++) { + PIOc_setframe(ncid, varid, rec); + PIOc_write_darray(ncid, varid, ioid, maplen, local_data, NULL); +} + +// 6. Close file +PIOc_closefile(ncid); + +// 7. Free decomposition +PIOc_freedecomp(iosysid, ioid); + +// 8. Finalize PIO +PIOc_finalize(iosysid); + +// 9. Finalize MPI +MPI_Finalize(); +``` + +### NetCDF Integration Workflow + +```c +// 1-2. Initialize MPI and IOsystem +MPI_Init(&argc, &argv); +nc_def_iosystem(MPI_COMM_WORLD, 1, 1, 0, 0, &iosysid); + +// 3. Define decomposition +nc_def_decomp(iosysid, PIO_FLOAT, ndims, dims, + maplen, compmap, &ioid, 1, NULL, NULL); + +// 4. Create file with NC_PIO flag +nc_create(filename, NC_PIO, &ncid); +nc_def_dim(ncid, "x", xdim, &dimids[0]); +nc_def_var(ncid, "data", NC_FLOAT, ndims, dimids, &varid); + +// 5. Write distributed data +nc_put_vard_float(ncid, varid, ioid, 0, local_data); + +// 6-8. Cleanup +nc_close(ncid); +nc_free_decomp(iosysid, ioid); +nc_free_iosystem(iosysid); +MPI_Finalize(); +``` + +## Performance Considerations + +### Choosing Number of I/O Tasks + +- **Too few**: I/O becomes bottleneck +- **Too many**: Communication overhead increases +- **Typical**: 1-10% of total tasks for I/O +- **Distribute**: Spread I/O tasks across nodes + +### Rearranger Selection + +- Start with `PIO_REARR_SUBSET` (usually better scaling) +- Try `PIO_REARR_BOX` if I/O performance is poor +- Test both with your specific decomposition + +### IOTYPE Selection + +- `PIO_IOTYPE_PNETCDF`: Best parallel performance for classic formats +- `PIO_IOTYPE_NETCDF4P`: Good parallel performance with compression +- Avoid sequential IOTYPEs (`NETCDF`, `NETCDF4C`) at scale + +### Decomposition Design + +- Balance load across processors +- Minimize fragmentation on I/O tasks +- Consider data access patterns (contiguous vs. strided) +- Reuse decompositions when possible + +## Debugging and Logging + +### Enable Logging + +```c +// Set log level (0=none, 1=errors, 2=warnings, 3=info, 4=debug) +PIOc_set_log_level(3); +``` + +```bash +# Build with logging support +./configure --enable-logging +cmake -DPIO_ENABLE_LOGGING=On +``` + +### Common Issues + +**Decomposition errors:** +- Ensure 1-based indexing for PIO API, 0-based for netCDF integration +- Check that all global array elements are covered +- Verify no overlapping elements between tasks + +**Performance issues:** +- Profile with different I/O task counts +- Test both rearrangers +- Check file system (Lustre striping, etc.) +- Monitor MPI communication patterns + +**Build issues:** +- Ensure all libraries built with same MPI compiler +- Check netCDF built with `--enable-parallel-tests` +- Verify HDF5 built with parallel support + +## Resources + +- **Documentation**: https://ncar.github.io/ParallelIO/ +- **GitHub**: https://github.com/NCAR/ParallelIO +- **Mailing List**: parallelio@googlegroups.com +- **Examples**: `examples/c/` and `examples/f03/` in source tree +- **NetCDF Integration Examples**: `tests/ncint/` and `tests/fncint/` + +## Quick Reference + +### Key Data Types + +**C:** +- `int iosysid` - IOsystem ID +- `int ncid` - File ID +- `int ioid` - Decomposition ID +- `PIO_Offset *compmap` - Decomposition map (1-based) + +**Fortran:** +- `type(iosystem_desc_t)` - IOsystem descriptor +- `type(file_desc_t)` - File descriptor +- `type(var_desc_t)` - Variable descriptor +- `type(io_desc_t)` - Decomposition descriptor + +### Essential Functions + +| Task | C Function | Fortran Subroutine | +|------|-----------|-------------------| +| Init Intracomm | `PIOc_Init_Intracomm()` | `PIO_init()` | +| Init Async | `PIOc_init_async()` | `PIO_init()` | +| Create decomp | `PIOc_InitDecomp()` | `PIO_initdecomp()` | +| Create file | `PIOc_createfile()` | `PIO_createfile()` | +| Write darray | `PIOc_write_darray()` | `PIO_write_darray()` | +| Read darray | `PIOc_read_darray()` | `PIO_read_darray()` | +| Free decomp | `PIOc_freedecomp()` | `PIO_freedecomp()` | +| Finalize | `PIOc_finalize()` | `PIO_finalize()` | diff --git a/windsurf-harnett/workflows/implement.md b/windsurf-harnett/workflows/implement.md new file mode 100644 index 000000000..1e3329344 --- /dev/null +++ b/windsurf-harnett/workflows/implement.md @@ -0,0 +1,130 @@ +--- +description: implement an issue +auto_execution_mode: 3 +--- + +# Issue Implementation Workflow + +This workflow implements GitHub issues by extracting planning information and following structured implementation plans. + +**Usage**: `/implement [issue_number]` or `Implement issue [issue_number]` + +## Implementation Process + +### Phase 1: Issue Analysis & Planning Extraction + +1. **Fetch GitHub Issue Details** + - Use `mcp0_issue_read` to get issue body, labels, and metadata + - Use `mcp0_issue_read` with `get_comments` to retrieve all comments + - Parse issue title, state, and assignee information + +2. **Extract Implementation Plan from Issue** + - Look for these sections in issue body and comments: + - "Implementation Plan", "Implementation Steps", or "Implementation Roadmap" + - "Requirements & Acceptance Criteria" + - "Technical Details" or "Technical Approach" + - "Dependencies" and "Testing Requirements" + - Extract numbered/bulleted task lists with time estimates + - Identify acceptance criteria with checkboxes + - Parse technical specifications and code examples + +3. **Create Task Management Structure** + - Initialize todo list with extracted implementation steps + - Mark first step as `in_progress` + - Include dependencies, testing, and documentation tasks + +### Phase 2: Context & Architecture Review + +4. **Review Project Documentation** + - Read `docs/design.md` for architecture and design patterns + - Read `docs/prd.md` for product requirements and specifications + - Check for relevant format-specific documentation (e.g., GeoTIFF, CDF) + +5. **Examine Codebase Context** + - Locate relevant source files mentioned in the issue + - Review existing implementation patterns + - Identify similar functionality for reference + - Check build system integration points + +### Phase 3: Structured Implementation + +6. **Execute Implementation Steps** + For each step in the extracted plan: + - **Code Implementation**: Write/modify code following existing patterns + - **Documentation Updates**: Update relevant docs if specified + - **Testing**: Implement unit/integration tests as required + - **Validation**: Verify acceptance criteria are met + - **Progress Tracking**: Mark step complete, update todo list + +7. **Handle Dependencies** + - Verify all dependencies are satisfied before implementation + - Check for blocking issues or prerequisites + - Ensure build system changes are consistent across CMake/Autotools + +### Phase 4: Quality Assurance & Completion + +8. **Testing & Validation** + - Run unit tests for new/modified code + - Execute integration tests with sample data + - Verify no regression in existing functionality + - Check memory management and error handling + +9. **Definition of Done Verification** + - All acceptance criteria met + - Tests passing with required coverage + - Documentation updated + - No regressions introduced + - Code follows project patterns + +10. **Final Integration** + - Run full test suite using `/btest` workflow + - Update GitHub issue with implementation status + - Mark issue as ready for review/close if appropriate + +## Implementation Guidelines + +### Code Quality Standards +- Follow existing code style and patterns +- Maintain backward compatibility +- Use established error handling patterns +- Ensure proper memory management +- Add appropriate logging and debugging support + +### Testing Requirements +- Unit tests for all new functions +- Integration tests for new features +- Error handling path testing +- Memory leak validation +- Performance testing if applicable + +### Documentation Standards +- Update API documentation for new functions +- Add inline comments for complex logic +- Update design documents if architecture changes +- Include usage examples in relevant docs + +## Error Handling + +- If issue lacks clear implementation plan, redirect to `/issue` workflow first +- If dependencies are missing, document and block implementation +- If tests fail, debug and fix before proceeding +- If acceptance criteria unclear, ask for clarification on GitHub issue + +## Progress Tracking + +- Use todo_list to track implementation steps +- Update GitHub issue with progress comments +- Mark tasks complete as they are finished +- Document any deviations from the original plan + +## Example Implementation Flow + +For issue 92 (CRS Metadata Extraction): +1. Fetch issue → Extract 7-step implementation plan +2. Review docs/design.md → Understand GeoTIFF architecture +3. Examine src/geotifffile.c → Locate placeholder code +4. Implement Step 1: Add CRS data structures → Update header +5. Implement Step 2: extract_crs_parameters() → Write function +6. Continue through all steps → Run tests → Update GitHub + +Example usage: "/implement 60" or "Implement issue 60" \ No newline at end of file diff --git a/windsurf-harnett/workflows/issue.md b/windsurf-harnett/workflows/issue.md new file mode 100644 index 000000000..c767fa0ae --- /dev/null +++ b/windsurf-harnett/workflows/issue.md @@ -0,0 +1,210 @@ +--- +description: Refine GitHub issues and generate implementation plans +--- + +# Issue Refinement Workflow + +This workflow helps refine GitHub issues systematically to clarify requirements, identify dependencies, and generate actionable implementation plans. + +**This workflow refines issues and generates implementation plans—no code implementation.** + +1. **Issue Assessment & Validation**: Review and validate the GitHub issue +2. **Documentation Research**: Consult project documentation for architectural context +3. **Codebase Context**: Examine existing code patterns and similar implementations +4. **Requirements Refinement**: Ask targeted clarifying questions +5. **Dependency Analysis**: Check for dependencies and conflicts +6. **Implementation Plan Generation**: Create structured, actionable plan +7. **GitHub Documentation**: Record refined requirements and implementation plan + +**The output is a refined issue with a clear implementation plan. Code implementation happens in a separate workflow.** + +--- + +## Step 1: Issue Analysis & Validation + +First, thoroughly examine the GitHub issue to understand: +- The problem description and context +- Current behavior vs. expected behavior +- Any error messages, logs, or screenshots provided +- Existing discussion or comments + +**Validate issue quality:** +- ✓ Issue title is clear and specific +- ✓ Problem description is understandable +- ✓ Expected behavior is defined +- ✓ At least one reproduction step (for bugs) +- ✓ Environment details provided (if relevant) +- **If issue is incomplete:** Ask for missing information before proceeding + +--- + +## Step 2: Consult Project Documentation + +Review the project's documentation to gather architectural context and development guidance: +- **Examine docs/design.md** for system architecture, design patterns, and technical specifications +- **Check docs/prd.md** for product requirements, feature specifications, and business requirements +- **Review docs/prfaq.md** for frequently asked questions, common issues, and implementation guidance +- **Check README.md** for build instructions, dependencies, and project overview +- Look for relevant design decisions or technical specifications in the documentation +- Understand established patterns and conventions used in the project + +--- + +## Step 3: Examine Codebase Context + +**Search for similar implementations:** +- Look for related functionality in `src/`, `include/`, `test/` +- Identify existing NC_Dispatch implementations to follow +- Review error handling patterns and NetCDF error codes used +- Check memory management patterns (allocation, cleanup) +- Note build system patterns (CMake, Autotools) + +**Check GitHub context:** +- Search for related existing issues (open and closed) +- Check for relevant pull requests +- Note any blocking or related issues +- Identify available milestones or project boards + +--- + +## Step 4: Clarifying Questions + +Ask 3-7 targeted questions to better understand the problem and requirements. Focus on: + +**For bug reports:** +- Reproduction steps and environment details +- Expected vs. actual behavior +- Error messages or logs +- When the issue started occurring +- Any recent changes that might be related + +**For feature requests:** +- Specific use cases and workflows +- Performance requirements or constraints +- Integration with existing functionality +- User interface considerations +- Backward compatibility requirements + +**For documentation issues:** +- Which documentation needs updating +- Target audience (users, developers, contributors) +- Missing or unclear information +- Examples that would be helpful + +Format questions as multiple choice with a recommended answer when appropriate. + +--- + +## Step 5: Dependency Analysis + +**Identify dependencies and conflicts:** +- Check if this issue depends on other open issues +- Look for potential conflicts with in-progress work +- Identify external dependencies (libraries, systems, teams) +- Note any blocking issues or prerequisites + +**Document dependencies:** +- List dependent issues with issue numbers +- Identify any work that must be completed first +- Note any conflicts that need resolution + +--- + +## Step 6: Implementation Plan Generation (NOT Implementation) + +Based on the answers provided, generate a structured implementation plan: + +**Plan Structure:** +```markdown +## Executive Summary + + +## Requirements & Acceptance Criteria +- [ ] +- [ ] +- [ ] + +## Implementation Approach + + +## Implementation Steps +1. - +2. - +3. - + +## Dependencies +- Depends on # - +- Blocks # - + +## Testing Requirements +- [ ] Unit tests for all new or modified functions +- [ ] Unit tests for error handling paths +- [ ] Integration test for +- [ ] Manual testing for +- [ ] Test coverage verification (minimum 80% for new code) + +## Risks & Mitigations +- + +## Notes + +``` + +**Quality checks:** +- ✓ Steps are concrete and actionable +- ✓ Dependencies are clearly identified +- ✓ Testing requirements are specific +- ✓ Effort estimates are reasonable +- ✓ Plan follows project conventions + +**Goal: Create a clear, actionable plan that can be executed in a separate implementation workflow.** + +--- + +## Step 7: GitHub Documentation + +Create a comprehensive follow-up comment on the GitHub issue that documents the refined requirements and implementation plan. This serves as both a record and a roadmap for future implementation. + +**Comment structure:** +```markdown +## Issue Refinement Summary + +### Executive Summary + + +### Requirements & Acceptance Criteria +- [ ] +- [ ] +- [ ] + +### Implementation Plan + + +### Next Steps + +``` + +**Posting to GitHub:** +Use the `gh` command line tool to post the comment to the issue: +```bash +gh issue comment --body "your comment text here" +``` + +--- + +## Step 8: Final Review & Implementation Roadmap + +Review the complete analysis for completeness and accuracy, then append the finalized implementation roadmap to the bottom of the GitHub follow-up comment. The roadmap should include: + +- A numbered list of concrete implementation steps (to be executed in a separate workflow) +- Owner/assignee for each major step +- Estimated completion dates or effort +- Dependencies between steps +- Success criteria for each milestone + +**Final validation:** +- ✓ All questions have been answered +- ✓ Requirements are clear and testable +- ✓ Implementation plan is technically sound +- ✓ Dependencies are documented +- ✓ Testing strategy is comprehensive diff --git a/windsurf-harnett/workflows/release.md b/windsurf-harnett/workflows/release.md new file mode 100644 index 000000000..149734c1d --- /dev/null +++ b/windsurf-harnett/workflows/release.md @@ -0,0 +1,163 @@ +--- +description: Generate GitHub release notes from issues and projects +auto_execution_mode: 3 +--- + +Generate professional GitHub release notes for a specific version by extracting information from GitHub issues, pull requests, and project boards, following the established release notes format. + +## Prerequisites +1. Identify the version to document (user will specify, e.g., "0.5.0", "1.0.0") +2. Check `docs/releases/` for the latest release notes format examples (v0.4.0.md is comprehensive) +3. Determine the GitHub milestone or project board associated with this version +4. Get GitHub repository details (owner: Intelligent-Data-Design-Inc, repo: NEP) + +## Steps + +### 1. Gather Issues and PRs for Version +Use GitHub MCP tools to collect: +- **Closed issues** in the version milestone (if milestone exists) +- **Merged pull requests** associated with the version +- **Project board items** (if using GitHub Projects for version tracking) +- **Release date**: Use the date of the last merged PR or current date + +Query strategy: +``` +# Search for issues closed in version +mcp0_search_issues: query="milestone:v{VERSION} is:closed" + +# Search for merged PRs in version +mcp0_search_pull_requests: query="is:merged milestone:v{VERSION}" + +# Alternative: Search by labels if no milestone +mcp0_search_issues: query="label:v{VERSION} is:closed" +``` + +### 2. Categorize Issues by Type +Analyze collected issues and PRs, grouping them by: +- **Features**: New functionality (label: enhancement, feature) +- **Bug Fixes**: Resolved issues (label: bug, fix) +- **Build System**: CMake, Autotools changes (label: build) +- **Testing**: Test coverage, new tests (label: testing) +- **Documentation**: Docs updates (label: documentation) +- **Dependencies**: New or updated libraries +- **API Changes**: NetCDF API extensions, dispatch changes +- **Performance**: Optimizations, benchmarks + +Extract from each issue: +- Title and issue number +- Description/summary +- Implementation details from PR description +- Related files/components modified + +### 3. Review Existing Release Notes Format +Examine the most recent release notes (e.g., `docs/releases/v0.4.0.md`) to understand: +- Section structure and order +- Writing style (professional, concise, technical) +- How features are organized by category +- Level of technical detail expected + +### 4. Create Release Notes Structure +Generate a markdown file named `docs/releases/v{VERSION}.md` with these sections: + +**Required Sections:** +- **Title & Subtitle**: Version number + main theme (derived from most impactful features), followed by `>` blockquote with one-line description +- **Highlights**: 4-6 bullet points of major achievements (start with bold emphasis, reference issue numbers) +- **Features**: Organized by functional areas with detailed descriptions + - For each feature: "**[Feature Name]** (#123) - Description with implementation details" + - Group by: UDF handlers, compression, build system, file format support, etc. +- **Bug Fixes**: List significant bugs resolved with issue numbers + - Format: "**[Bug description]** (#456) - Resolution details" +- **API Changes**: New/modified NetCDF API extensions, NC_Dispatch implementations (if applicable) +- **Build System**: CMake and Autotools changes, new dependencies, configuration options +- **Testing**: Coverage metrics, test strategies, new test files added +- **Documentation**: Doxygen updates, API documentation, user guides (if applicable) +- **Dependencies Added**: New libraries (HDF5, NetCDF-C, NCEPLIBS-g2, etc.) with versions +- **Known Limitations**: Open issues or deferred work (link to GitHub issues) +- **Breaking Changes**: None or list with migration guidance + +**Footer Section:** +- **Released**: Date (format: YYYY-MM-DD, use last merged PR date or current date) +- **Scope**: List GitHub milestone or project board name +- **Issues Closed**: Total count of issues/PRs included +- **Contributors**: GitHub usernames of PR authors (if multiple contributors) + +### 5. Writing Guidelines +- **Be concise**: Keep total length under 100-120 lines (~1 page when rendered) +- **Use technical language**: Include file paths, function names, specific implementations +- **Reference issues**: Always include issue/PR numbers in format (#123) +- **Emphasize user impact**: Start features with user-facing benefits +- **Cross-reference docs**: Link to architecture docs, API specs, testing guides +- **Maintain consistency**: Match tone and structure of previous releases +- **Prioritize information**: Most important features first, details second +- **Credit contributors**: Mention PR authors when appropriate + +### 6. Content Transformation Rules +When converting GitHub issues/PRs to release notes: +- **Issue titles → Feature descriptions**: Expand with context and implementation details from PR +- **PR descriptions → Implementation details**: Extract what was built, where in codebase, which files modified +- **Labels → Section assignment**: Use issue labels to categorize into appropriate sections +- **Closed issues → Highlights**: Select 4-6 most impactful closed issues for Highlights section +- **Open issues → Known Limitations**: Reference deferred or incomplete work +- **PR merge dates → Release date**: Use the latest merge date as release date +- **Multiple related issues → Single feature**: Combine related issues into cohesive feature descriptions + +### 7. Quality Checks +Before finalizing: +- ✅ Version number matches milestone/project (format: v0.X.0 or v1.X.0) +- ✅ File name matches version (e.g., `v0.5.0.md` for Version 0.5.0) +- ✅ All issue numbers are valid and link correctly (#123 format) +- ✅ All sections present (even if "None" or "N/A") +- ✅ Length is under 100-120 lines +- ✅ No placeholder text like "[TODO]" or "[INSERT]" +- ✅ Technical terms are accurate (verify file paths, function names) +- ✅ Consistent formatting (bold for emphasis, code blocks for file paths) +- ✅ Issue/PR counts match actual GitHub data +- ✅ Contributors are credited appropriately + +### 8. Finalization +- Save the file to `docs/releases/v{VERSION}.md` +- Do NOT commit or push (per user rules) +- Provide summary: "Release notes for v{VERSION} generated from {N} issues and {M} PRs" +- Inform the user the release notes are ready for review + +## Example Usage + +**Scenario 1: Version with Milestone** +``` +User: "Create release notes for version 0.5.0" +→ Search GitHub for milestone:v0.5.0 issues and PRs +→ Categorize by labels (enhancement, bug, build, etc.) +→ Generate `docs/releases/v0.5.0.md` +→ Follow format from latest release in docs/releases/ +→ Include issue numbers (#123) for all features/fixes +→ Keep under 120 lines +``` + +**Scenario 2: Version with Label-based Tracking** +``` +User: "Generate release notes for v0.6.0" +→ Search for label:v0.6.0 closed issues +→ Search for merged PRs with v0.6.0 label +→ Extract implementation details from PR descriptions +→ Group by functional area (GeoTIFF, CDF, build system, etc.) +→ Generate release notes with proper attribution +``` + +**Scenario 3: Version from Project Board** +``` +User: "Create release notes from project 'Version 1.0 Release'" +→ Query GitHub project board items +→ Filter for completed/closed items +→ Extract linked issues and PRs +→ Generate comprehensive release notes +→ Credit all contributors +``` + +## Notes +- If no milestone/label/project exists for the version, ask user to clarify which issues/PRs to include +- If some issues are still open, note them in "Known Limitations" section +- Adapt section order based on version content (e.g., skip "Dependencies" if none added) +- Use past tense for completed work, present tense for capabilities added +- Always verify issue/PR numbers are valid before including in release notes +- For versions with many issues (>20), prioritize most impactful features in Highlights +- If PR descriptions are sparse, read the actual code changes to understand implementation details \ No newline at end of file diff --git a/windsurf-harnett/workflows/review.md b/windsurf-harnett/workflows/review.md new file mode 100644 index 000000000..7fe1ec9c2 --- /dev/null +++ b/windsurf-harnett/workflows/review.md @@ -0,0 +1,71 @@ +--- +description: Review pull request and add code review comments +--- + +# Pull Request Code Review Workflow + +This workflow performs a comprehensive code review on a pull request and adds review comments directly to the PR. + +## Steps + +1. **Fetch pull request details** + - Get PR information using the pull request number + - Retrieve the list of changed files + - Get the diff for each changed file + - Identify added, modified, and deleted lines + +2. **Perform comprehensive code review** + - Analyze code quality and adherence to best practices + - Identify potential bugs or edge cases + - Look for performance optimization opportunities + - Assess readability and maintainability + - Check for security concerns + - Review documentation completeness + - Check for code duplication + - Verify error handling patterns + - Ensure unit tests are included for new/modified code + +3. **Generate review comments** + - Create a list of code review comments for each issue found + - For each comment, include: + - **File**: The file being commented on + - **Line**: Specific line number (or range) + - **Type**: Suggestion/Issue/Question/Praise + - **Comment**: Detailed feedback or question + - **Severity**: Critical/High/Medium/Low + +4. **Add review comments to PR** + - For each review comment, ask: "Add this comment to PR? (yes/no)" + - If user says **yes**: Add the comment as a PR review comment using the appropriate line + - If user says **no**: Skip and move to next comment + - Continue until all comments are processed + +5. **Submit overall review** + - Ask user for overall review status: "Approve", "Request Changes", or "Comment" + - Add summary comment with overall assessment + - Submit the review with the chosen status + +6. **Summary** + - Report how many comments were added + - List the types of issues found + - Note the overall review status + +## Usage + +``` +/review +``` + +Example: +``` +/review 123 +``` + +## Notes + +- Comments are added as pull request review comments on specific lines +- Use appropriate comment types (suggestion, issue, question, praise) +- Include code references using GitHub's line selection +- Review focuses on code quality, not creating separate issues +- For critical issues that need separate tracking, suggest creating issues in comments +- Group related issues when appropriate \ No newline at end of file diff --git a/windsurf-harnett/workflows/roadmap.md b/windsurf-harnett/workflows/roadmap.md new file mode 100644 index 000000000..4d0e38b58 --- /dev/null +++ b/windsurf-harnett/workflows/roadmap.md @@ -0,0 +1,217 @@ +--- +description: Break version work into detailed sprint planning documents +--- + +# Version Roadmap Planning Workflow + +This workflow breaks down a version's work into 1-5 detailed sprint planning documents. + +## Usage + +``` +/roadmap +``` + +**Example:** `/roadmap v1.6.0 Add CF conventions for GeoTIFF metadata` + +**Output:** Creates version-specific GitHub Project "NEP Version 1.6.0 - GeoTIFF metadata to CF" with sprint breakdown + +**Note:** Only v1.6.0+ versions are supported. Historical versions are not updated. + +--- + +## Scope + +**Do:** +- Analyze the specified version's work and break it into 1-5 sprints +- Identify technical gaps and missing requirements across all sprints +- Create version-specific GitHub Project with sprint breakdown and task organization +- Ensure logical dependency flow between sprints + +**Don't:** +- Modify other versions not specified +- Implement code (save for `/plan` workflow) +- Create more than 5 sprints for a version +- Skip dependency analysis between sprints +- Add code changes to GitHub Project (use Issues/PRs instead) +- Update historical versions (v1.5.0 and earlier) - these are frozen + +--- + +## Project Creation Process + +### Naming Convention +Version-specific projects follow the pattern: `NEP Version {version} - {brief description}` + +**Examples:** +- "NEP Version 1.6.0 - GeoTIFF metadata to CF" +- "NEP Version 1.7.0 - GRIB2 write support" +- "NEP Version 1.8.0 - Performance optimization" + +### Project Structure +Each version-specific project includes: +- **Organization-level project** (not repository-specific) +- **Standard fields**: Version, Sprint, Task Status, Priority, Component +- **Field values**: + - Version: "{version}" for all issues + - Sprint: 1-5 for respective sprint issues + - Task Status: Backlog → In Progress → Review → Done + - Priority: High/Medium/Low + - Component: Build System/UDF Handler/Tests/Documentation +- **Issues**: Main version issue + individual sprint issues + +### Implementation Tools +- **GitHub CLI** (`gh project create`) for automated creation +- **GitHub API** as fallback for advanced configuration +- **Manual creation** option when automation fails + +--- + +## Steps + +### 1. Validate version and create version-specific GitHub Project + +- Check if version-specific GitHub Project exists for NEP organization +- **If no project exists:** Create new organization-level GitHub Project with: + - Project name: "NEP Version {version} - {brief description}" (e.g., "NEP Version 1.6.0 - GeoTIFF metadata to CF") + - Owner: Intelligent-Data-Design-Inc (organization level) + - Fields: Version (text), Sprint (number), Task Status (single select: Backlog, In Progress, Review, Done), Priority (single select: High, Medium, Low), Component (single select: Build System, UDF Handler, Tests, Documentation) + - Add all relevant issues to the project +- Query GitHub Projects for the specified version +- **If version is v1.5.0 or earlier:** Report that historical versions are not supported and stop workflow + +### 2. Extract version details + +- Locate the specified version in GitHub Project +- **If version exists but has no items:** Create initial project items for the version based on: + - User-provided version description from the command prompt + - Known technical components and requirements + - Standard sprint structure (file operations, metadata, data reading, etc.) +- Extract all existing work items, goals, and requirements from project items +- Identify any existing sprint breakdown from project fields +- Note incomplete or ambiguous requirements from item descriptions +- Assess total scope and complexity from linked Issues/PRs + +### 3. Review related documentation for context + +- Check `docs/prd.md` for product requirements and functional specifications +- Review `docs/design.md` for architecture and technical design +- Review existing sprint plans in `docs/plan/` to understand format and detail level +- Check source code in `src/`, `include/`, and `test/` for existing patterns + +### 4. Analyze version scope and identify optimal sprint breakdown + +- Review all work items for completeness and clarity +- Use the user-provided version description as the primary source for requirements +- Group related tasks into logical sprint units (1-5 sprints total) +- Identify dependencies between task groups +- Ensure each sprint has a coherent theme and deliverable +- Check for missing implementation details (NC_Dispatch functions, data structures, error handling) +- Verify testing requirements (C unit tests, Fortran tests, integration tests) +- Assess build system integration (CMake and Autotools) +- Identify dependency requirements and version constraints + +### 5. Ask 3-6 numbered clarifying questions + +**Focus areas:** +- Sprint organization and dependency flow based on user-provided description +- Ambiguous requirements or missing technical details from the initial prompt +- Error handling strategies and NetCDF error code mapping +- Testing expectations and coverage targets per sprint +- Build system integration and dependency detection +- Memory management and resource cleanup +- Integration with native format libraries (NCEPLIBS-g2, libgeotiff, NASA CDF) + +**Question format:** +- Provide suggested answers, labeled with letters, so questions can be answered with just a letter +- Recommend an answer and provide justification for your recommendation +- Ask minimum 3 questions, maximum 6 questions + +**⚠️ ASK QUESTIONS NOW** + +**Wait for user responses before proceeding** + +**If answers are incomplete:** +- Ask follow-up questions to clarify +- Maximum 2 rounds of questions total + +### 6. Update roadmap with sprint breakdown + +- Incorporate answers from step 5 into GitHub Project +- Create 1-5 sprint sections using project fields and organization +- Distribute work items logically across sprints by updating item fields +- Clarify ambiguous tasks with specific details in item descriptions +- Add dependency information between project items +- Update definition of done for each sprint in project metadata +- Ensure each sprint builds incrementally toward version goals +- Link related Issues/PRs to project items for implementation + +**Important:** +- **DO NOT** add proposed code changes to project item descriptions +- Code implementation should be tracked in separate Issues/PRs linked to project items +- If example code is presented by the user, that may be included in item descriptions + +### 7. Update related documentation (if needed) + +**Only update if clarifications revealed:** +- Architecture changes → Update `docs/design.md` +- Functional requirement changes → Update `docs/prd.md` +- NC_Dispatch or UDF handler design notes → Add to `docs/design.md` + +**Skip this step if:** No architectural or functional changes were clarified + +### 8. Verify completion criteria + +**Confirm all items are satisfied:** +- ✓ Version work is broken into 1-5 logical sprints +- ✓ Each sprint has a coherent theme and clear deliverables +- ✓ Dependencies between sprints are documented +- ✓ All sprint tasks are clearly defined +- ✓ Implementation approach is specified for each major task +- ✓ Testing requirements are identified for each sprint +- ✓ Build system integration is addressed +- ✓ Dependencies are documented +- ✓ Error handling strategy is defined +- ✓ GitHub Project is updated with all clarifications and sprint organization + +**If any criteria are missing:** Return to step 5 and ask additional questions + +**Next step:** User can run `/plan sprint ` to create detailed implementation plan for individual sprints + +--- + +## Error Handling + +**If version not found:** +- Report "Version {version} not found in GitHub Project" +- List available versions from project +- Stop workflow + +**If version is v1.5.0 or earlier:** +- Report "Historical versions (v1.5.0 and earlier) are not supported" +- Stop workflow + +**If version scope is too large for 5 sprints:** +- Report: "Version {version} work is too complex for 5 sprints" +- Suggest breaking into multiple versions or reducing scope +- Ask user how to proceed + +**If version has insufficient work for 1 sprint:** +- Report: "Version {version} has minimal work, consider combining with another version" +- Ask user if they want to proceed with a single sprint + +**If dependencies not met:** +- Report: "Previous version dependencies not satisfied" +- List missing dependencies +- Ask user if they want to proceed anyway + +**If GitHub Project creation fails:** +- Report specific GitHub API errors for project creation +- Suggest manual project creation with the naming convention "NEP Version {version} - {brief description}" +- Provide field configuration details for manual setup +- Stop workflow + +**If GitHub Project operations fail:** +- Report specific GitHub API errors +- Suggest manual project updates as fallback +- Continue with documentation updates if possible diff --git a/windsurf-harnett/workflows/test.md b/windsurf-harnett/workflows/test.md new file mode 100644 index 000000000..80554fa3f --- /dev/null +++ b/windsurf-harnett/workflows/test.md @@ -0,0 +1,103 @@ +--- +description: run C/Fortran tests and validate implementation +auto_execution_mode: 3 +--- + +# C/Fortran Testing and Validation Workflow + +This workflow runs all C and Fortran tests to validate implementation against issue requirements. + +## Prerequisites + +Ensure the project is built with both CMake and Autotools build systems: +```bash +cd /home/ed/NEP +# CMake build +mkdir -p build && cd build +cmake .. +make +cd .. +# Autotools build +./autogen.sh +mkdir -p build_autotools && cd build_autotools +../configure +make +cd .. +``` + +## Steps + +### 1. Parse Issue Implementation Plan +- Read the GitHub issue to identify testing requirements from the implementation plan +- Extract specific test scenarios mentioned in the issue +- Note any special testing requirements (edge cases, error conditions, etc.) + +### 2. Run CMake Tests (C tests) +// turbo +```bash +cd /home/ed/NEP/build +ctest --output-on-failure +``` +**Expected:** All C tests pass. Tests include LZ4 compression tests and UDF handler tests. + +### 3. Run Autotools Tests (C tests) +// turbo +```bash +cd /home/ed/NEP/build_autotools/test +./run_tests.sh +``` +**Expected:** All C tests pass with proper HDF5 plugin path configuration. + +### 4. Run Fortran Tests (if built) +// turbo +```bash +cd /home/ed/NEP/build_autotools/ftest +./run_tests.sh +``` +**Expected:** All Fortran tests pass. Tests include nep compression tests. + +### 5. Verify Test Coverage Requirements +// turbo +```bash +cd /home/ed/NEP/build +# Generate coverage report +gcov -r ../src/*.c ../src/*.h +# Calculate coverage percentage +coverage=$(gcov -r ../src/*.c | grep "Lines executed:" | awk '{sum += $3} END {print sum/NR}') +echo "Coverage: $coverage%" +``` +**Expected:** Minimum 80% coverage for new/modified code as specified in issue requirements. + +### 6. Check for Compiler Warnings +```bash +cd /home/ed/NEP/build +make clean +make VERBOSE=1 2>&1 | grep -i "warning" +``` +**Expected:** No new warnings introduced. Review any warnings and fix if critical. + +### 7. Report Results to GitHub Issue +Post a summary comment on the GitHub issue with test results: +- Test execution status (pass/fail) +- Coverage percentage (meets requirements?) +- Any warnings found +- Link to detailed test logs if needed + +## Success Criteria + +✅ **CMake Tests**: All CTest tests pass (LZ4, UDF handlers) +✅ **Autotools C Tests**: All tests in test/run_tests.sh pass +✅ **Fortran Tests**: All tests in ftest/run_tests.sh pass (if Fortran enabled) +✅ **Test Coverage**: Minimum 80% coverage for new/modified code +✅ **Compiler Warnings**: No critical warnings introduced +✅ **GitHub Reporting**: Results posted to issue with clear status + +## Troubleshooting + +- **Test failures**: Check test output for specific failures, verify HDF5_PLUGIN_PATH is set correctly +- **Missing dependencies**: Ensure HDF5, NetCDF-C, NetCDF-Fortran, and LZ4 are installed +- **Plugin path issues**: Verify HDF5_PLUGIN_PATH includes the LZ4 plugin directory +- **Build failures**: Clean build directories and rebuild from scratch +- **GRIB2 test failures**: Ensure NCEPLIBS-g2 is installed if GRIB2 UDF handler is enabled + +**ALL TESTS MUST PASS, NO TESTS MAY BE LEFT BROKEN** \ No newline at end of file From 27cf83f8883e2e6d46ef85727a8729b560d40346 Mon Sep 17 00:00:00 2001 From: James Gallagher Date: Mon, 9 Mar 2026 19:25:54 -0600 Subject: [PATCH 3/4] Used codex to write and implement a plan to streamline configure.ac The plan is in the docs dir as configure-ac-refactor-plan.md --- conf/libdap.m4 | 110 +++++ configure.ac | 301 ++------------ docs/AGENTS.md | 109 +++++ docs/configure-ac-refactor-plan.md | 276 +++++++++++++ gtest-migration-plan.md | 282 +++++++++++++ windsurf-harnett/skills/pio/skill.md | 591 --------------------------- 6 files changed, 815 insertions(+), 854 deletions(-) create mode 100644 docs/AGENTS.md create mode 100644 docs/configure-ac-refactor-plan.md create mode 100644 gtest-migration-plan.md delete mode 100644 windsurf-harnett/skills/pio/skill.md diff --git a/conf/libdap.m4 b/conf/libdap.m4 index 54d1aede9..c2ababcb9 100644 --- a/conf/libdap.m4 +++ b/conf/libdap.m4 @@ -312,3 +312,113 @@ dnl AC_SUBST([DAP_CLIENT_STATIC_LIBS]) dnl AC_SUBST([DAP_SERVER_STATIC_LIBS]) dnl AC_SUBST([DAP_ROOT]) ]) + +# LIBDAP_CHECK_CURL() +# Resolve libcurl flags, preferring pkg-config and falling back to curl-config. +AC_DEFUN([LIBDAP_CHECK_CURL], +[ + AC_ARG_WITH([curl], + [AS_HELP_STRING([--with-curl=PFX],[curl/libcurl prefix; overrides pkg-config and curl-config fallback])], + [with_curl_prefix="$withval"], + [with_curl_prefix=""]) + + curlprivatereq= + curlprivatelibs= + libdap_libcurl_module='libcurl >= 7.19.0' + + AC_MSG_CHECKING([for libcurl]) + + AS_IF([test -n "$with_curl_prefix"], + [ + CURL_CONFIG="$with_curl_prefix/bin/curl-config" + AS_IF([test ! -x "$CURL_CONFIG"], + [AC_MSG_ERROR([You set the curl prefix directory to $with_curl_prefix, but curl-config is not there.])]) + + CURL_LIBS=`$CURL_CONFIG --libs` + CURL_CFLAGS=`$CURL_CONFIG --cflags` + curlprivatelibs=$CURL_LIBS + AC_MSG_RESULT([yes; used $CURL_CONFIG]) + ], + [ + PKG_CHECK_MODULES([CURL],[$libdap_libcurl_module], + [ + curlprivatereq=$libdap_libcurl_module + curlprivatelibs=`$PKG_CONFIG --static --libs libcurl` + AC_MSG_RESULT([yes; used pkg-config]) + ], + [ + AC_PATH_PROG([CURL_CONFIG], [curl-config], [no]) + AS_IF([test "x$CURL_CONFIG" = xno], + [AC_MSG_ERROR([I could not find libcurl])]) + + version_libcurl=`$CURL_CONFIG --version | sed 's@libcurl \(.*\)@\1@'` + AS_VERSION_COMPARE(["$version_libcurl"], ["7.19.0"], + [AC_MSG_ERROR([I could not find libcurl 7.19.0 or newer, found $version_libcurl])]) + + CURL_LIBS=`$CURL_CONFIG --libs` + CURL_CFLAGS=`$CURL_CONFIG --cflags` + curlprivatelibs=$CURL_LIBS + AC_MSG_RESULT([yes; used curl-config and found version $version_libcurl]) + ]) + ]) + + AC_SUBST([curlprivatereq]) + AC_SUBST([curlprivatelibs]) + AC_SUBST([CURL_LIBS]) + AC_SUBST([CURL_CFLAGS]) +]) + +# LIBDAP_CHECK_XML2() +# Resolve libxml2 flags, preferring pkg-config and falling back to xml2-config. +AC_DEFUN([LIBDAP_CHECK_XML2], +[ + AC_ARG_WITH([xml2], + [AS_HELP_STRING([--with-xml2=PFX],[libxml2 prefix; overrides pkg-config and xml2-config fallback])], + [with_xml2_prefix="$withval"], + [with_xml2_prefix=""]) + + xmlprivatereq= + xmlprivatelibs= + libdap_libxml2_module='libxml-2.0 >= 2.7.0' + + AC_MSG_CHECKING([for libxml2]) + + AS_IF([test -n "$with_xml2_prefix"], + [ + XML2_CONFIG="$with_xml2_prefix/bin/xml2-config" + AS_IF([test ! -x "$XML2_CONFIG"], + [AC_MSG_ERROR([You set the libxml2 prefix directory to $with_xml2_prefix, but xml2-config is not there.])]) + + XML2_LIBS=`$XML2_CONFIG --libs` + XML2_CFLAGS=`$XML2_CONFIG --cflags` + xmlprivatelibs=$XML2_LIBS + AC_MSG_RESULT([yes; used $XML2_CONFIG]) + ], + [ + PKG_CHECK_MODULES([XML2],[$libdap_libxml2_module], + [ + xmlprivatereq=$libdap_libxml2_module + xmlprivatelibs=`$PKG_CONFIG --libs libxml-2.0` + AC_MSG_RESULT([yes; used pkg-config]) + ], + [ + AC_PATH_PROG([XML2_CONFIG], [xml2-config], [no]) + AS_IF([test "x$XML2_CONFIG" = xno], + [AC_MSG_ERROR([I could not find libxml2])]) + + version_libxml2=`$XML2_CONFIG --version` + AS_VERSION_COMPARE(["$version_libxml2"], ["2.7.0"], + [AC_MSG_ERROR([I could not find libxml2 2.7.0 or newer])]) + + XML2_LIBS=`$XML2_CONFIG --libs` + XML2_CFLAGS=`$XML2_CONFIG --cflags` + xmlprivatelibs=$XML2_LIBS + AC_MSG_RESULT([yes; used xml2-config and found version $version_libxml2]) + ]) + ]) + + AC_SUBST([xmlprivatereq]) + AC_SUBST([xmlprivatelibs]) + AC_SUBST([XML2_LIBS]) + AC_SUBST([XML2_CFLAGS]) +]) diff --git a/configure.ac b/configure.ac index 4ab001e68..4b4d113fa 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,3 @@ - dnl -*- autoconf -*- dnl Process this file with autoconf to produce a configure script. @@ -57,15 +56,6 @@ AC_DEFINE_UNQUOTED(CVER, "$PACKAGE_VERSION", [Client version number]) AC_DEFINE_UNQUOTED(DVR, "libdap/$PACKAGE_VERSION", [Client name and version combined]) AC_SUBST(DVR) -dnl This block of code can be removed since the DDS now only builds the v3.2 DDX. -dnl jhrg 2/4/22 -dnl -dnl Use one of these two blocks to build a DAP2 or DAP 3.2 DDX -dnl AC_DEFINE(DAP2_DDX, 1, [Build the DAP 2 version of the DDX]) -dnl AC_SUBST(DAP2_DDX) -dnl AC_DEFINE(DAP3_2_DDX, 1, [Build the DAP 3.2 version of the DDX]) -dnl AC_SUBST(DAP3_2_DDX) - AS_IF([echo $PACKAGE_VERSION | grep -q '^\([[0-9]]\)*\.\([[0-9]]*\)\.\([[0-9]]*\)-\([[0-9]]*\)$'], [PACKAGE_MAJOR_VERSION=`echo $PACKAGE_VERSION | sed 's@^\([[0-9]]\)*\.\([[0-9]]*\)\.\([[0-9]]*\)-\([[0-9]]*\)$@\1@'` PACKAGE_MINOR_VERSION=`echo $PACKAGE_VERSION | sed 's@^\([[0-9]]\)*\.\([[0-9]]*\)\.\([[0-9]]*\)-\([[0-9]]*\)$@\2@'` @@ -197,17 +187,7 @@ AS_VERSION_COMPARE(["$bison_version"], ["3.0"], AC_MSG_RESULT([found version $bison_version]) dnl Checks for header files. -AC_HEADER_DIRENT -# Use this once we can update to autoconf 2.70. jhrg 2/13/25 AC_CHECK_INCLUDES_DEFAULT -AC_PROG_EGREP -AC_CHECK_HEADERS([getopt.h limits.h locale.h]) - - -AC_HEADER_SYS_WAIT - -AC_CHECK_HEADERS_ONCE([fcntl.h malloc.h memory.h stddef.h stdlib.h string.h strings.h unistd.h pthread.h]) -AC_CHECK_HEADERS_ONCE([sys/param.h sys/time.h]) -AC_CHECK_HEADERS_ONCE([netinet/in.h]) +AC_CHECK_HEADERS_ONCE([stdlib.h string.h unistd.h pthread.h]) dnl Do this because we have had a number of problems with the UUID header/library AC_CHECK_HEADERS([uuid/uuid.h],[found_uuid_uuid_h=true],[found_uuid_uuid_h=false]) @@ -215,13 +195,6 @@ AC_CHECK_HEADERS([uuid.h],[found_uuid_h=true],[found_uuid_h=false]) AS_IF([test $found_uuid_uuid_h = true -o $found_uuid_h = true], [], [AC_MSG_ERROR([Could not find uuid.h])]) -dnl Checks for typedefs, structures, and compiler characteristics. -AC_C_CONST -AC_C_INLINE -AC_TYPE_SIZE_T -AC_CHECK_MEMBERS([struct stat.st_blksize]) -AC_STRUCT_TM -AC_C_VOLATILE AC_C_BIGENDIAN # This is used by the DMR tests which must choose the correct set of baselines @@ -243,7 +216,7 @@ AS_IF([test "x$enable_runtime_endian_check" = "xyes"], [ # Checks for library functions. dnl using AC_CHECK_FUNCS does not run macros from gnulib. -AC_CHECK_FUNCS([alarm atexit bzero dup2 getcwd getpagesize localtime_r memmove memset pow putenv setenv strchr strerror strtol strtoul timegm mktime]) +AC_CHECK_FUNCS([atexit timegm mktime]) gl_SOURCE_BASE(gl) gl_M4_BASE(gl/m4) @@ -251,210 +224,8 @@ gl_MODULES(regex btyeswap) gl_INIT -AC_ARG_WITH([curl], [AS_HELP_STRING([--with-curl=pfx],[curl/libcurl prefix; overrides other tests including pkgconfig])], - with_curl_prefix="$withval", with_curl_prefix="") - -dnl I wrote these checks because we need the *-config scripts to build, so -dnl the AC_CHECK_LIB macro is not needed. - -curlprivatereq= -curlprivatelibs= -curl_set= - -if test -n "$with_curl_prefix" -a -x $with_curl_prefix/bin/curl-config -then - AC_MSG_NOTICE([Using $with_curl_prefix as the curl prefix directory.]) - CURL_LIBS="`$with_curl_prefix/bin/curl-config --libs`" - CURL_STATIC_LIBS=$CURL_LIBS - curlprivatelibs="`$with_curl_prefix/bin/curl-config --libs`" - CURL_CFLAGS="`$with_curl_prefix/bin/curl-config --cflags`" - curl_set="yes" -elif test -n "$with_curl_prefix" -then - AC_MSG_ERROR([You set the curl-prefix directory to $with_curl_prefix, but curl-config is not there.]) -fi - -if test -z "$curl_set" -then - # curlprivatereq= - # curlprivatelibs= - libdap_pkgconfig_libcurl=yes - libdap_libcurl_module='libcurl >= 7.19.0' - PKG_CHECK_MODULES([CURL],[$libdap_libcurl_module],, - [libdap_pkgconfig_libcurl=no]) - AC_MSG_CHECKING([for libcurl]) - - if test $libdap_pkgconfig_libcurl = 'yes' - then - curlprivatereq=$libdap_libcurl_module - CURL_STATIC_LIBS="`$PKG_CONFIG --static --libs libcurl`" - AC_MSG_RESULT([yes; used pkg-config]) - elif curl-config --version > /dev/null 2>&1 - then - version_libcurl=`curl-config --version | sed 's@libcurl \(.*\)@\1@'` - - AS_VERSION_COMPARE(["$version_libcurl"], ["7.19.0"], - [AC_MSG_ERROR([I could not find libcurl 7.19.0 or newer, found $version_libcurl])]) - - CURL_LIBS="`curl-config --libs`" - CURL_STATIC_LIBS=$CURL_LIBS - curlprivatelibs="`curl-config --libs`" - CURL_CFLAGS="`curl-config --cflags`" - AC_MSG_RESULT([yes; used curl-config and found version $version_libcurl]) - else - AC_MSG_ERROR([I could not find libcurl]) - fi -fi - -AC_SUBST([curlprivatereq]) -AC_SUBST([curlprivatelibs]) -AC_SUBST([CURL_LIBS]) -AC_SUBST([CURL_STATIC_LIBS]) -AC_SUBST([CURL_CFLAGS]) - -dnl ******** new version, breaks the bes package step ******** -dnl jhrg 8/31/20 -dnl -dnl AC_ARG_WITH(xml2, -dnl [AS_HELP_STRING([--with-xml2=PFX],[dnl Prefix where libxml2 is installed (optional). This will override pkgconfig, etc.])], -dnl [with_xml2_prefix="$withval"], -dnl [with_xml2_prefix=""]) -dnl -dnl AS_IF([test -n "$with_xml2_prefix" -a ! -x $with_xml2_prefix/bin/xml2-config], -dnl [AC_MSG_ERROR([You set the libxml2 prefix directory to $with_xml2_prefix, but xml2-config is not there.])]) -dnl -dnl xmlprivatereq= -dnl xmlprivatelibs= -dnl xml_set= -dnl -dnl # I changed this code so that it searches for libxml2 using xml2-config -dnl # first, then pkg-config. This works more reliably when working on OSX -dnl # given Apple's penchant for moving the lib. jhrg 8/25/20 -dnl -dnl AS_IF([test -n "$with_xml2_prefix" -a -x $with_xml2_prefix/bin/xml2-config], -dnl [ -dnl AC_MSG_NOTICE([Using $with_xml2_prefix as the libxml2 prefix directory.]) -dnl XML2_LIBS="`$with_xml2_prefix/bin/xml2-config --libs`" -dnl XML2_CFLAGS="`$with_xml2_prefix/bin/xml2-config --cflags`" -dnl xml_set="yes" -dnl ]) -dnl -dnl # Try using the xml2-config script. -dnl -dnl AS_IF([test -z "$xml_set" & xml2-config --version > /dev/null 2>&1], -dnl [ -dnl AC_MSG_CHECKING([for libxml2]) -dnl version_libxml2=`xml2-config --version` -dnl -dnl AS_VERSION_COMPARE(["$version_libxml2"], ["2.7.0"], -dnl [AC_MSG_ERROR([I could not find libxml2 2.7.0 or newer])]) -dnl -dnl XML2_LIBS="`xml2-config --libs`" -dnl XML2_CFLAGS="`xml2-config --cflags`" -dnl xmlprivatelibs="`xml2-config --libs `" -dnl -dnl # If XML2_CFLAGS does not have -I that ends in /libxml2, append that to -dnl # the string bound to -I. Assume there is only on -I in XML2_CFLAGS. jhrg 8/25/20 -dnl AS_IF([echo $XML2_CFLAGS | grep -v -e '-I.*/libxml2'], -dnl [XML2_CFLAGS=`echo $XML2_CFLAGS | sed "s@\(-I.*\)@\1/libxml2/@g"`]) -dnl -dnl AC_MSG_RESULT([yes; used xml2-config and found version $version_libxml2]) -dnl xml_set=yes -dnl ]) -dnl -dnl # If not found, try pkg-config -dnl AS_IF([test -z "$xml_set"], -dnl [ -dnl libdap_libxml2_module='libxml-2.0 >= 2.7.0' -dnl PKG_CHECK_MODULES([XML2], [libdap_libxml2_module], -dnl [libdap_pkgconfig_libxml2=yes], -dnl [libdap_pkgconfig_libxml2=no]) -dnl AS_IF([test $libdap_pkgconfig_libxml2 = yes], -dnl [ -dnl XML2_LIBS="`$PKG_CONFIG --libs libxml-2.0`" -dnl XML2_CFLAGS="`$PKG_CONFIG --cflags libxml-2.0`" -dnl AC_MSG_RESULT([yes; used pkg-config]) -dnl xmlprivatereq=$libdap_libxml2_module -dnl xml_set=yes -dnl ], -dnl [ -dnl AC_MSG_ERROR([I could not find xml2-config]) -dnl ]) -dnl ]) -dnl -dnl AS_IF([test -z "xml_set"], -dnl [ -dnl AC_MSG_ERROR([I could not find xml2-config]) -dnl ]) -dnl -dnl AC_SUBST([xmlprivatereq]) -dnl AC_SUBST([xmlprivatelibs]) -dnl AC_SUBST([XML2_LIBS]) -dnl AC_SUBST([XML2_CFLAGS]) -dnl -dnl ******** end new, broken-for-the-bes version ********* - -dnl Version of XML2 configuration from git commit 8624abec8e3d510508c3c97ac60082700995af2c -dnl jhrg 8/31/20 - -AC_ARG_WITH(xml2,[ --with-xml2=PFX Prefix where libxml2 is installed (optional). This will override pkgconfig, etc.], - with_xml2_prefix="$withval", with_xml2_prefix="") - -xmlprivatereq= -xmlprivatelibs= -xml_set= - -if test -n "$with_xml2_prefix" -a -x $with_xml2_prefix/bin/xml2-config -then - AC_MSG_NOTICE([Using $with_xml2_prefix as the libxml2 prefix directory.]) - XML2_LIBS="`$with_xml2_prefix/bin/xml2-config --libs`" - dnl XML2_STATIC_LIBS=$XML2_LIBS - xmlprivatelibs="`$with_xml2_prefix/bin/xml2-config --libs`" - XML2_CFLAGS="`$with_xml2_prefix/bin/xml2-config --cflags`" - xml_set="yes" -elif test -n "$with_xml2_prefix" -then - AC_MSG_ERROR([You set the libxml2 prefix directory to $with_xml2_prefix, but xml2-config is not there.]) -fi - -if test -z "$xml_set" -then -libdap_pkgconfig_libxml2=yes -libdap_libxml2_module='libxml-2.0 >= 2.7.0' -PKG_CHECK_MODULES([XML2],[$libdap_libxml2_module], ,[libdap_pkgconfig_libxml2=no]) -AC_MSG_CHECKING([for libxml2]) -if test $libdap_pkgconfig_libxml2 = 'yes' -then - xmlprivatereq=$libdap_libxml2_module - dnl XML2_STATIC_LIBS="`$PKG_CONFIG --static --libs libxml-2.0`" - XML2_LIBS="`$PKG_CONFIG --libs libxml-2.0`" - AC_MSG_RESULT([yes; used pkg-config]) -elif xml2-config --version > /dev/null 2>&1 -then - version_libxml2=`xml2-config --version` - - AS_VERSION_COMPARE(["$version_libxml2"], ["2.7.0"], - [AC_MSG_ERROR([I could not find libxml2 2.7.0 or newer])]) - - XML2_LIBS="`xml2-config --libs`" - dnl XML2_STATIC_LIBS=$XML2_LIBS - XML2_CFLAGS="`xml2-config --cflags`" - xmlprivatelibs="`xml2-config --libs `" - dnl ` - AC_MSG_RESULT([yes; used xml2-config and found version $version_libxml2]) -else - AC_MSG_ERROR([I could not find xml2-config]) -fi -fi - -AC_SUBST([xmlprivatereq]) -AC_SUBST([xmlprivatelibs]) -AC_SUBST([XML2_LIBS]) -dnl AC_SUBST([XML2_STATIC_LIBS]) -AC_SUBST([XML2_CFLAGS]) - -dnl End old version of XML2 configuration. jhrg 8/31/20 -dnl ******** +LIBDAP_CHECK_CURL +LIBDAP_CHECK_XML2 dnl Check for the RHEL 8 requirement libtirpc and its headers. dnl jhrg 6/23/22 @@ -476,20 +247,20 @@ AC_CHECK_LIB([crypto], [OpenSSL_add_all_algorithms], [CRYPTO_LIBS=""]) AC_SUBST([CRYPTO_LIBS]) -# AM_PATH_CPPUNIT(1.12.0, -# [AM_CONDITIONAL([CPPUNIT], [true])], -# [ -# PKG_CHECK_MODULES(CPPUNIT, [cppunit >= 1.12.0], -# [AM_CONDITIONAL([CPPUNIT], [true])], -# [AM_CONDITIONAL([CPPUNIT], [false])] -# ) -# ] -# ) - -AM_PATH_CPPUNIT(1.12.0, - [AM_CONDITIONAL([CPPUNIT], [true])], - [AM_CONDITIONAL([CPPUNIT], [false])] -) +AC_ARG_ENABLE([cppunit], + [AS_HELP_STRING([--disable-cppunit], [Skip CppUnit detection and disable CppUnit-based tests (default: detect)])], + [], + [enable_cppunit=auto]) + +AS_IF([test "x$enable_cppunit" = xno], + [CPPUNIT_CFLAGS= + CPPUNIT_LIBS= + AM_CONDITIONAL([CPPUNIT], [false])], + [AM_PATH_CPPUNIT(1.12.0, + [AM_CONDITIONAL([CPPUNIT], [true])], + [AM_CONDITIONAL([CPPUNIT], [false])])]) +AC_SUBST([CPPUNIT_CFLAGS]) +AC_SUBST([CPPUNIT_LIBS]) DODS_DEBUG_OPTION @@ -503,8 +274,10 @@ AC_ARG_ENABLE([asan], dnl Removed -fsanitize=undefined. jhrg 2/21/25 -ASAN_OPTIONS="-fsanitize=address -fno-omit-frame-pointer" -CXX_FLAGS_CHECK([$ASAN_OPTIONS], [has_asan=yes], [has_asan=no]) +has_asan=no +AS_IF([test x$enable_asan = xyes], + [ASAN_OPTIONS="-fsanitize=address -fno-omit-frame-pointer" + CXX_FLAGS_CHECK([$ASAN_OPTIONS], [has_asan=yes], [has_asan=no])]) AS_IF([test x$enable_asan = xyes -a x$has_asan = xyes], [AC_MSG_NOTICE([Building Address Sanitizer version]) @@ -525,9 +298,9 @@ AS_IF([test x$enable_batest = xyes ], AC_ARG_ENABLE([leaks], [AS_HELP_STRING([--enable-leaks], [Run unit tests on OSX using the 'leaks' if available (default: no)])]) -# The 'leaks' tool on OSX can be used to test if a program leaks memory. -# Look for 'leaks' and set LEAKS to it if found, else set it to 'no' -AC_CHECK_PROG(LEAKS, [leaks], [leaks], [no]) +LEAKS=no +AS_IF([test x$enable_leaks = xyes], + [AC_CHECK_PROG(LEAKS, [leaks], [leaks], [no])]) AS_IF([test x$enable_leaks = xyes -a x$LEAKS != xno], [AC_MSG_NOTICE([Will run unit-tests using leaks]) @@ -553,18 +326,20 @@ AS_IF([test x$enable_developer = xyes], AC_ARG_ENABLE([coverage], [AS_HELP_STRING([--enable-coverage], [Build so tests emit coverage data and enable coverage target (default: no)])]) -AC_CHECK_LIB([gcov], [gcov_open], [GCOV_LIBS="-lgcov"], [GCOV_LIBS=]) - -AS_IF([test x$enable_coverage = xyes && which gcov], - [AC_MSG_NOTICE([Building coverage version]) - AM_CONDITIONAL([ENABLE_COVERAGE], [true]) - AS_IF([gcov -help | grep LLVM], - [GCOVR_FLAGS=], - [GCOVR_FLAGS="-k -e '.*Test.cc' -e '.*T.cc' -e '.*-test.cc'" - LIBS="-lgcov $LIBS"])], +GCOVR_FLAGS= +AS_IF([test x$enable_coverage = xyes], + [AC_PATH_PROG([GCOV], [gcov], [no]) + AS_IF([test "x$GCOV" != xno], + [AC_MSG_NOTICE([Building coverage version]) + AM_CONDITIONAL([ENABLE_COVERAGE], [true]) + AS_IF([$GCOV -help 2>&1 | grep -q LLVM], + [GCOVR_FLAGS=], + [GCOVR_FLAGS="-k -e '.*Test.cc' -e '.*T.cc' -e '.*-test.cc'" + LIBS="-lgcov $LIBS"])], + [AC_MSG_NOTICE([Not building coverage version]) + AC_MSG_NOTICE([Check that gcov is on your PATH]) + AM_CONDITIONAL([ENABLE_COVERAGE], [false])])], [AC_MSG_NOTICE([Not building coverage version]) - AS_IF([test x$enable_coverage = xyes], - [AC_MSG_NOTICE([Check that gcov is on your PATH])]) AM_CONDITIONAL([ENABLE_COVERAGE], [false])]) AC_SUBST([GCOVR_FLAGS]) diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 000000000..43c546363 --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,109 @@ +# AGENTS.md + +## Scope + +These instructions apply to the entire `libdap4` repository. + +## Project Context + +- `libdap4` is a legacy C++ implementation of DAP2/DAP4 with long-lived downstream consumers. +- Prioritize compatibility, behavioral stability, and small, reviewable diffs. +- Prefer minimal, targeted changes over broad refactors. + +## Primary Build Systems + +- Prefer autotools for day-to-day work unless the task is explicitly CMake-focused. +- Keep both autotools and CMake build paths healthy when changing shared build logic. + +## Autotools Workflow (preferred) + +For a fresh git checkout: + +```sh +autoreconf --force --install --verbose +./configure --prefix=$prefix --enable-developer +make -j +make -j check +``` + +For release-tarball style builds: + +```sh +./configure +make -j +make -j check +``` + +Notes: + +- Check that the environment variable 'prefix' is defined before running any command that uses it. +- Use `--prefix=` when installation path matters. +- Use `TESTSUITEFLAGS=-j` with `make check` when parallelizing tests. +- If `make check` fails due to missing `config.guess`, link `conf/config.guess` into `tests/` per `tests/README`. + +## CMake Workflow (supported) + +- Presets are defined in `CMakePresets.json`. +- Common presets: `default`, `debug`, `developer`, `asan`. + +Typical flow: + +```sh +cmake --preset developer +cmake --build --preset developer -j +ctest --preset developer --output-on-failure +``` + +## Testing Expectations + +- For code changes, run focused tests in affected areas first, then broader suites when risk is higher. +- Autotools default: `make -j check` +- CMake default: `ctest --preset default` (or `developer` for debug/developer builds) +- Unit/integration labels are available through CMake test presets (`unit`, `int`). +- If tests are flaky or expected-fail in legacy areas, call that out explicitly in your summary. + +## Documentation And Doxygen + +- Doxygen docs are built with: + +```sh +make docs +``` + +- Inputs are `doxy.conf` and `main_page.doxygen` (generated from `.in` templates by configure). +- When updating doc config/templates, keep generated and template files consistent with the chosen build workflow. + +## Legacy C++ Constraints + +- Match local style in touched files; do not perform unrelated formatting sweeps. +- Avoid API/ABI-impacting changes unless explicitly requested. +- Be conservative with ownership/lifetime changes in pointer-heavy code. +- Parser/scanner sources are generated (`*.tab.cc`, `*.tab.hh`, `lex.*.cc`); edit `*.yy`/`*.lex` sources, not generated outputs, unless the task explicitly requires generated-file updates. + +## Tooling And Quality + +- `clang-format` and pre-commit are configured (`README.pre-commit.md`, `.pre-commit-config.yaml`). +- Prefer running formatting/hooks only on changed files relevant to the task. +- Address sanitizer is supported (`--enable-asan` in autotools, `asan` preset in CMake) for memory-safety debugging. + +## Change Discipline + +- Do not revert unrelated local changes in a dirty worktree. +- Keep edits tightly scoped to the request. +- If you encounter unexpected repository changes during work, stop and ask how to proceed. +- Do not run destructive git commands unless explicitly requested. + +## Review Priorities + +When asked to review: + +1. Behavioral regressions in protocol/data-model behavior +2. Memory/resource safety and ownership lifetime issues +3. Parser/serialization correctness and edge cases +4. Build-system regressions (autotools and CMake) +5. Missing or weak regression coverage + +## Communication + +- State assumptions and environment details explicitly (build system, preset/configure flags, test scope). +- If full validation is not run, say exactly what was run and what was not. diff --git a/docs/configure-ac-refactor-plan.md b/docs/configure-ac-refactor-plan.md new file mode 100644 index 000000000..a7b2bf3c3 --- /dev/null +++ b/docs/configure-ac-refactor-plan.md @@ -0,0 +1,276 @@ +# `configure.ac` Refactoring Plan + +## Goal + +Reduce `./configure` runtime first, and make `configure.ac` easier to maintain second, by removing probes that no longer affect compilation, linking, tests, or generated build files. + +## Current observations + +The current `configure.ac` mixes four different kinds of logic: + +1. Required dependency discovery that still feeds `Makefile.am`, `dap-config`, or `libdap.pc`. +2. Optional build/test toggles that still drive Automake conditionals. +3. Legacy portability probes that define `HAVE_*` symbols no longer consumed by the code. +4. Historical commented-out blocks and shell-heavy custom logic that increase maintenance cost. + +A quick symbol audit against the tree shows several header and function checks now appear unused outside generated files: + +### Header probes with no current consumer + +- `HAVE_GETOPT_H` +- `HAVE_LIMITS_H` +- `HAVE_FCNTL_H` +- `HAVE_MEMORY_H` +- `HAVE_STDDEF_H` +- `HAVE_NETINET_IN_H` + +### Function probes with no current consumer + +- `HAVE_ALARM` +- `HAVE_BZERO` +- `HAVE_GETCWD` +- `HAVE_LOCALTIME_R` +- `HAVE_MEMMOVE` +- `HAVE_MEMSET` +- `HAVE_POW` +- `HAVE_PUTENV` +- `HAVE_STRCHR` +- `HAVE_STRERROR` +- `HAVE_STRTOL` +- `HAVE_STRTOUL` + +There are also legacy/commented sections that add noise without affecting the generated build: + +- The old DAP2/DDX block near the top. +- A large commented-out alternate libxml2 detection implementation. +- Commented-out CppUnit detection alternatives. + +## Refactoring strategy + +Apply the work in phases so behavior stays stable while the script gets smaller and faster. + +## Phase 1: Build an evidence-backed inventory + +Before deleting checks, produce a short inventory that classifies every probe in `configure.ac` as: + +- `required`: affects compile flags, link flags, generated scripts, or test baselines +- `optional`: controls developer/test-only behavior +- `dead`: no current consumer in source or Automake input +- `replace`: still needed, but should use a simpler macro or less shell code + +This inventory should include: + +- `AC_CHECK_HEADERS*` +- `AC_CHECK_FUNCS` +- `AC_CHECK_LIB` +- `PKG_CHECK_MODULES` +- `AM_CONDITIONAL` +- custom macros such as `DODS_CHECK_SIZES`, `OX_RHEL8_TIRPC`, and `DODS_DEBUG_OPTION` + +Deliverable: a checked-in table or comment block that makes future cleanup decisions auditable. + +## Phase 2: Remove dead portability probes first + +This is the safest runtime win. + +### Remove unused header checks + +Delete header probes whose `HAVE_*` symbols have no current non-generated consumer, starting with: + +- `getopt.h` +- `limits.h` +- `fcntl.h` +- `memory.h` +- `stddef.h` +- `netinet/in.h` + +Then review low-value probes with only one or two consumers and replace them with unconditional standard includes where practical: + +- `malloc.h` +- `sys/time.h` +- `sys/param.h` +- `locale.h` + +For each remaining conditional include, decide whether the project still cares about the target platforms that required it. If not, remove both the probe and the `#ifdef HAVE_...` branches in code. + +### Remove unused function checks + +Delete unused entries from the large `AC_CHECK_FUNCS([...])` list. Keep only functions whose `HAVE_*` symbols still affect source behavior, such as: + +- `atexit` +- `dup2` +- `getpagesize` +- `setenv` +- `timegm` +- `mktime` + +If some of those can now be assumed on supported platforms, remove their probes too and simplify the code accordingly. + +Expected result: fewer compile/link test fragments generated and executed by `configure`, with minimal behavior risk. + +## Phase 3: Replace obsolete Autoconf portability macros + +Several generic portability macros are legacy baggage for a modern C++14 library. + +Review and likely remove or justify: + +- `AC_HEADER_DIRENT` +- `AC_HEADER_SYS_WAIT` +- `AC_C_CONST` +- `AC_C_INLINE` +- `AC_TYPE_SIZE_T` +- `AC_STRUCT_TM` +- `AC_C_VOLATILE` +- `AC_CHECK_MEMBERS([struct stat.st_blksize])` + +These should stay only if one of the following is true: + +- a generated symbol is still used in the code, or +- the project explicitly supports platforms old enough to require the probe + +If there is no such support requirement, remove them. For a C++14 codebase, many of these are unnecessary. + +## Phase 4: Simplify dependency detection + +Most remaining configure time is likely spent in external dependency checks, compiler/link tests, and shelling out to helper tools. + +### Curl and libxml2 + +The current curl and libxml2 blocks duplicate logic across: + +- explicit `--with-...` prefixes +- `pkg-config` +- `*-config` scripts + +Refactor each dependency probe into one small macro with this order: + +1. honor explicit `--with-...` prefix +2. try `PKG_CHECK_MODULES` +3. fall back to `curl-config` or `xml2-config` only if required for platforms still in scope + +Also: + +- move each dependency block into `conf/*.m4` or a local helper macro +- delete the large commented-out alternate libxml2 implementation +- keep only the variables actually consumed by the build (`*_LIBS`, `*_CFLAGS`, private pkg-config fields) + +If the supported platforms all provide `pkg-config`, the biggest runtime and maintenance win is to make `pkg-config` the only non-prefix path and drop `curl-config`/`xml2-config` fallback logic entirely. + +### Library checks + +Review `AC_CHECK_LIB` use for: + +- `pthread` +- `uuid` +- `crypto` +- `gcov` + +If these libraries are already discovered transitively through pkg-config or are guaranteed on supported platforms, avoid redundant link probes. In particular: + +- `pthread` may be better handled with standard thread detection macros instead of a raw `-lpthread` check. +- `gcov` should be checked only when `--enable-coverage` is requested, not on every run. + +## Phase 5: Make optional developer/test features lazy + +Some probes should only run when the related feature is requested. + +### Coverage + +Move all coverage detection behind `--enable-coverage=yes`: + +- `AC_CHECK_LIB([gcov], ...)` +- `which gcov` +- `gcov -help | grep LLVM` + +No coverage-related work should happen in the default configure path. + +### Leaks + +Only call `AC_CHECK_PROG(LEAKS, ...)` when `--enable-leaks=yes` is requested. On non-macOS builders this is pure overhead. + +### CppUnit + +CppUnit detection should run only if unit tests that require it will be built. If test builds are optional, gate `AM_PATH_CPPUNIT` behind an explicit option such as `--enable-cppunit-tests` or equivalent test toggle. + +### AddressSanitizer + +The ASan compiler flag probe is reasonable, but it should only run when `--enable-asan=yes` is requested. + +Expected result: the common `./configure` path avoids feature checks for coverage, leaks, ASan, and optional test frameworks. + +## Phase 6: Replace shell-heavy parsing with simpler M4/shell patterns + +The version parsing and some helper checks currently use repeated `grep`/`sed` pipelines. They are not the main runtime cost, but they do make the script harder to maintain. + +Refactor to: + +- parse version components once +- avoid repeated external `sed` calls where shell parameter expansion or a single helper macro is enough +- consolidate repeated `if test -n "$prefix" -a -x ...` patterns into helper macros + +Also clean up: + +- commented historical notes that no longer guide current behavior +- duplicated or stale comments about removed build paths + +## Phase 7: Reorganize the file for maintainability + +After functional cleanup, split `configure.ac` into clearer sections: + +1. package/version setup +2. toolchain setup +3. core compile environment +4. required dependencies +5. optional developer/test features +6. generated files + +Move reusable dependency logic into local `.m4` macros under `conf/` so `configure.ac` becomes mostly orchestration rather than embedded shell script. + +Recommended first extractions: + +- `LIBDAP_CHECK_CURL` +- `LIBDAP_CHECK_XML2` +- `LIBDAP_CHECK_OPTIONAL_TEST_TOOLS` + +## Suggested implementation order + +1. Remove dead `AC_CHECK_HEADERS*` entries. +2. Remove dead `AC_CHECK_FUNCS` entries. +3. Gate coverage, leaks, ASan, and CppUnit detection behind explicit enable options. +4. Delete commented-out legacy blocks. +5. Simplify curl and libxml2 detection. +6. Review/remove obsolete portability macros. +7. Extract the remaining dependency logic into local `.m4` helpers. + +This order gives early runtime improvements before the larger structural cleanup. + +## Validation plan + +For each cleanup step, verify both generated configuration and real builds. + +Minimum validation matrix: + +- `autoreconf -fi` +- default `./configure` +- `./configure --enable-developer` +- `./configure --enable-asan` if supported by the compiler +- `./configure --enable-coverage` +- one build with explicit `--with-curl=...` or `--with-xml2=...` if those paths are retained + +Then run: + +- a normal library build +- at least one unit-test build +- the Autotest suite in `tests/` + +Also measure configure runtime before and after each major phase so the cleanup stays aligned with the primary goal. + +## Success criteria + +The refactor is complete when: + +- every remaining probe has a documented consumer +- default `./configure` runs fewer external checks than today +- optional developer/test probes are skipped unless explicitly requested +- `configure.ac` no longer contains large commented-out alternative implementations +- dependency detection logic is short enough that a maintainer can reason about it without reading historical branches diff --git a/gtest-migration-plan.md b/gtest-migration-plan.md new file mode 100644 index 000000000..92f4cffce --- /dev/null +++ b/gtest-migration-plan.md @@ -0,0 +1,282 @@ +# GoogleTest Migration Plan For Autotools Unit Tests + +## Scope + +This plan covers only the CppUnit-based tests that are built and run through the autotools `make check` path. + +Included: +- `unit-tests/Makefile.am` +- `http_dap/unit-tests/Makefile.am` +- `d4_ce/unit-tests/Makefile.am` + +Excluded: +- CMake test wiring +- The autotest integration suites in `tests/Makefile.am`, except for shared test support such as `libtest-types.a` + +The current autotools CppUnit surface is 63 test binaries: +- 59 in `unit-tests/` +- 3 in `http_dap/unit-tests/` +- 1 in `d4_ce/unit-tests/` + +This plan uses `AGENTS.md` guidance and the architecture/build notes in `docs/deep-dive-codex.md`. + +## Goals + +- Replace CppUnit with GoogleTest for the autotools unit-test executables +- Preserve `make check` behavior for autotools users +- Keep the existing test executable names stable during migration +- Avoid changing the autotest integration suites unless needed for shared support code +- Minimize risk by converting low-coupling tests first and network-sensitive tests last + +## Build-System Plan + +1. Replace the configure-time dependency check in `configure.ac`. + Remove the `AM_PATH_CPPUNIT(...)` dependency gate and introduce a GoogleTest detection path with a new automake conditional such as `GTEST`. + +2. Add GoogleTest build variables to the autotools test directories. + In `unit-tests/Makefile.am`, `http_dap/unit-tests/Makefile.am`, and `d4_ce/unit-tests/Makefile.am`, replace `$(CPPUNIT_CFLAGS)` and `$(CPPUNIT_LIBS)` with GoogleTest equivalents such as `$(GTEST_CFLAGS)`, `$(GTEST_LIBS)`, and `$(PTHREAD_LIBS)`. + +3. Keep the autotools execution model stable. + Preserve `check_PROGRAMS = $(UNIT_TESTS)` and `TESTS = $(UNIT_TESTS)` so `make check` still builds and runs the same executables. + +4. Introduce a shared GoogleTest runner helper. + Replace `run_tests_cppunit.h` with a GoogleTest-compatible helper that preserves: + - `-d` / `-D` debug flag handling + - basic help behavior + - optional single-test selection from the command line + +5. Keep dual-framework support during the transition. + Do not remove CppUnit support until all autotools test binaries have been migrated and verified. This avoids turning the work into a single high-risk cutover. + +6. Remove CppUnit only after parity is reached. + After all autotools tests pass under GoogleTest, remove: + - CppUnit configure checks + - CppUnit conditionals in autotools files + - CppUnit headers and helper files + - CppUnit references in build/install documentation + +## Test Conversion Rules + +Use one consistent conversion pattern across the tree: + +- `CPPUNIT_TEST_SUITE` to `TEST_F` +- `CPPUNIT_ASSERT(expr)` to `EXPECT_TRUE(expr)` or `ASSERT_TRUE(expr)` +- `CPPUNIT_ASSERT_EQUAL(a, b)` to `EXPECT_EQ(a, b)` +- `CPPUNIT_ASSERT_THROW(expr, Ex)` to `EXPECT_THROW(expr, Ex)` +- `CPPUNIT_FAIL(msg)` to `FAIL() << msg` +- `setUp()` / `tearDown()` to `SetUp()` / `TearDown()` + +Preserve test logic during the framework switch: +- keep fixture setup and cleanup behavior unchanged +- keep environment-variable setup unchanged +- keep test asset paths and generated `test_config.h` usage unchanged +- do not rename executables in the first migration pass + +## Recommended Batches + +The batches are ordered to establish the GoogleTest pattern on low-risk tests first, then move outward toward parser, translation, cache, and HTTP/network-sensitive areas. + +### Batch 0: Build Skeleton And Proof Of Pattern + +Purpose: +- land the autotools GoogleTest dependency wiring +- add the shared runner/helper +- prove the conversion style in one test per subtree + +Tests: +- `unit-tests/BaseTypeTest` +- `d4_ce/unit-tests/D4ConstraintEvaluatorTest` +- `http_dap/unit-tests/HTTPConnectTest` + +Exit criteria: +- `autoreconf -fi` +- `./configure` +- each converted binary builds and runs under `make check` + +### Batch 1: Scalar And Utility Core Tests + +Purpose: +- convert low-coupling tests with straightforward fixtures and assertions +- validate the assertion mapping and runner helper + +Tests: +- `unit-tests/RegexTest` +- `unit-tests/ByteTest` +- `unit-tests/MIMEUtilTest` +- `unit-tests/generalUtilTest` +- `unit-tests/parserUtilTest` +- `unit-tests/ErrorTest` +- `unit-tests/SignalHandlerTest` +- `unit-tests/Int8Test` +- `unit-tests/Int16Test` +- `unit-tests/UInt16Test` +- `unit-tests/Int32Test` +- `unit-tests/UInt32Test` +- `unit-tests/Int64Test` +- `unit-tests/UInt64Test` +- `unit-tests/Float32Test` +- `unit-tests/Float64Test` + +Why this batch: +- mostly local assertions +- little shared state +- low filesystem and network sensitivity + +### Batch 2: Core DAP2 Model And Container Tests + +Purpose: +- convert the core object-model tests that exercise the library boundaries described in `docs/deep-dive-codex.md` + +Tests: +- `unit-tests/ArrayTest` +- `unit-tests/GridTest` +- `unit-tests/AttrTableTest` +- `unit-tests/DASTest` +- `unit-tests/DDSTest` +- `unit-tests/SequenceTest` +- `unit-tests/BaseTypeFactoryTest` +- `unit-tests/D4BaseTypeFactoryTest` +- `unit-tests/ConstraintEvaluatorTest` +- `unit-tests/ServerFunctionsListUnitTest` +- `unit-tests/BaseTypeTest` + +Why this batch: +- still mostly in-process +- builds confidence in fixture conversion before older legacy-style tests are touched + +### Batch 3: Legacy CppUnit Pattern Tests + +Purpose: +- convert the older tests that still use the historic `*T` naming and older fixture style + +Tests: +- `unit-tests/marshT` +- `unit-tests/arrayT` +- `unit-tests/attrTableT` +- `unit-tests/structT` +- `unit-tests/sequenceT` +- `unit-tests/ddsT` +- `unit-tests/dasT` +- `unit-tests/ancT` +- `unit-tests/util_mitTest` + +Why this batch: +- these are likely to require the most mechanical cleanup +- separating them avoids slowing down the cleaner modern conversions + +### Batch 4: Parser, XML, And Translation Tests + +Purpose: +- migrate the tests that depend on XML parsing, generated config, and DAP2/DAP4 translation paths + +Tests: +- `unit-tests/DDXParserTest` +- `unit-tests/D4ParserSax2Test` +- `unit-tests/DMRTest` +- `unit-tests/DmrRoundTripTest` +- `unit-tests/DmrToDap2Test` +- `unit-tests/IsDap4ProjectedTest` +- `unit-tests/D4AttributesTest` +- `unit-tests/D4DimensionsTest` +- `unit-tests/D4EnumDefsTest` +- `unit-tests/D4EnumTest` +- `unit-tests/D4GroupTest` +- `unit-tests/D4SequenceTest` +- `unit-tests/D4FilterClauseTest` +- `unit-tests/D4AsyncDocTest` + +Why this batch: +- these tests are tightly tied to the DAP4 parsing and translation flows +- they are a good midpoint between simple unit tests and stream/cache tests + +### Batch 5: Marshaller, Stream, And Concurrency Tests + +Purpose: +- convert tests that exercise serialization, streaming, and threading behavior + +Tests: +- `unit-tests/MarshallerTest` +- `unit-tests/MarshallerFutureTest` +- `unit-tests/MarshallerThreadTest` +- `unit-tests/D4MarshallerTest` +- `unit-tests/D4UnMarshallerTest` +- `unit-tests/D4StreamRoundTripTest` +- `unit-tests/chunked_iostream_test` + +Why this batch: +- more sensitive to fatal vs non-fatal assertions +- often easier to debug after the general fixture strategy is already proven + +### Batch 6: Cache And Local Filesystem Tests + +Purpose: +- convert tests that create, clean, or inspect local cache state and generated files + +Tests: +- `unit-tests/RCReaderTest` +- `unit-tests/DAPCache3Test` +- `unit-tests/ResponseCacheTest` if re-enabled for autotools +- `http_dap/unit-tests/HTTPCacheTest` + +Why this batch: +- fixture cleanup matters +- stale temp files and cache locks can hide migration bugs + +### Batch 7: HTTP And External-Environment Tests + +Purpose: +- convert the most environment-sensitive tests last + +Tests: +- `http_dap/unit-tests/HTTPConnectTest` +- `http_dap/unit-tests/HTTPThreadsConnectTest` + +Why this batch: +- network behavior and remote state can obscure framework migration issues +- these should be used only after the helper, fixture, and assertion patterns are stable + +### Batch 8: Optional Resource-Heavy Test + +Purpose: +- convert the largest and least convenient test only after the main migration is complete + +Tests: +- `unit-tests/BigArrayTest` + +Why this batch: +- already optional under autotools +- high runtime and resource cost +- poor candidate for early validation + +## Batch Notes + +- `HTTPConnectTest` appears in Batch 0 as a proof case and again in Batch 7 as part of the full HTTP sweep. Treat Batch 0 as the first pilot conversion for that subtree, then complete the remaining HTTP tests together. +- `ResponseCacheTest` is present in the source tree but not listed in `unit-tests/Makefile.am` for `UNIT_TESTS`. If it is intentionally dormant, do not expand scope during the migration. If it is meant to be restored, move it into Batch 6. +- Keep `test_config.h`, `testFile.cc`, `remove_directory.cc`, and `../tests/libtest-types.a` untouched unless the GoogleTest transition forces a narrowly scoped change. + +## Verification Plan + +After each batch: + +1. Run `autoreconf -fi` +2. Run `./configure` +3. Run the relevant subtree target: + - top-level `make check` + - or focused checks in `unit-tests/`, `http_dap/unit-tests/`, and `d4_ce/unit-tests/` +4. Re-run the converted binaries individually when debugging fixture behavior + +Before removing CppUnit entirely: + +1. Run a full top-level `make check` +2. Run at least one developer-style build variant if that is part of normal project use +3. Confirm that the autotest integration suites in `tests/` still build and run unchanged + +## Completion Criteria + +The migration is complete when: + +- all autotools unit-test executables build against GoogleTest +- `make check` succeeds through the autotools path +- CppUnit is no longer referenced by `configure.ac` or the autotools unit-test `Makefile.am` files +- the shared runner helper has been replaced with the GoogleTest version +- build and install documentation no longer claim that CppUnit is required for autotools unit tests diff --git a/windsurf-harnett/skills/pio/skill.md b/windsurf-harnett/skills/pio/skill.md deleted file mode 100644 index 05c8158cb..000000000 --- a/windsurf-harnett/skills/pio/skill.md +++ /dev/null @@ -1,591 +0,0 @@ -# PIO (Parallel I/O) and Parallel I/O with NetCDF - -Understanding the ParallelIO (PIO) library for high-performance parallel I/O with netCDF on HPC systems. Use when writing parallel netCDF applications, optimizing I/O performance on many processors, or converting existing netCDF codes to use PIO. - -## Core Concepts - -### What is PIO? - -PIO is a high-level parallel I/O C and Fortran library for structured grid applications that provides: -- A netCDF-like API for familiar programming patterns -- Ability to designate a subset of processors for I/O operations -- Support for both synchronous and asynchronous I/O modes -- Integration with netCDF, parallel-netcdf (PnetCDF), and HDF5 libraries -- Distributed array decomposition and reassembly - -### Key Benefits - -- **Scalability**: Reduces I/O contention on HPC systems with many processors -- **Flexibility**: Choose between different I/O modes and strategies -- **Performance**: Optimizes data movement between compute and I/O tasks -- **Compatibility**: Works with existing netCDF code with minimal changes (via netCDF integration) - -## Architecture - -### Library Structure - -``` -Fortran Application Code - ↓ -PIO Fortran Library (wrapper) - ↓ -PIO C Library (core functionality) - ↓ - ┌────┴────┐ - ↓ ↓ -netCDF-C PnetCDF - ↓ - HDF5 + compression (optional) -``` - -### Dependencies - -**Required:** -- netCDF-C library (version 4.6.1+, built with MPI for parallel I/O) -- MPI-enabled C and Fortran compilers - -**Optional:** -- PnetCDF (version 1.9.0+) for parallel classic format I/O -- HDF5 (MPI-enabled) for netCDF-4 support -- Compression libraries (zlib, szip) - -## I/O Modes - -### Intracomm Mode - -All processors participate in computation, with a subset designated for I/O: -- I/O processors also do computational work -- Simpler setup for single computational units -- I/O tasks typically distributed across nodes to avoid bottlenecks - -**Initialization:** -```c -// C -int iosysid; -PIOc_Init_Intracomm(MPI_COMM_WORLD, niotasks, ioproc_stride, - ioproc_start, PIO_REARR_SUBSET, &iosysid); - -// With netCDF integration -nc_def_iosystem(MPI_COMM_WORLD, niotasks, ioproc_stride, - ioproc_start, PIO_REARR_SUBSET, &iosysid); -``` - -```fortran -! Fortran -call PIO_init(my_rank, MPI_COMM_WORLD, niotasks, num_aggregator, & - stride, PIO_rearr_subset, pio_iosystem, base=1) -``` - -### Async Mode - -Dedicated I/O processors service multiple computational units: -- I/O processors do NO computational work -- Multiple computation components can share I/O processors -- I/O processors wait in message loop for requests -- Better for complex multi-component applications - -**Initialization:** -```c -// C -int iosysid; -PIOc_init_async(world_comm, num_io_procs, io_proc_list, - component_count, num_procs_per_comp, - proc_list, io_comm, comp_comm, &iosysid); - -// With netCDF integration -nc_def_async(world_comm, num_io_procs, io_proc_list, - component_count, num_procs_per_comp, - proc_list, &iosysid); -``` - -## IOTYPEs (I/O Methods) - -PIO supports four I/O types, specified when creating/opening files: - -| IOTYPE | Library | Format | Parallel I/O | Compression | Notes | -|--------|---------|--------|--------------|-------------|-------| -| `PIO_IOTYPE_PNETCDF` | PnetCDF | Classic, 64-bit offset, CDF5 | Yes | No | Parallel classic formats only | -| `PIO_IOTYPE_NETCDF` | netCDF-C | Classic, 64-bit offset | No | No | Sequential, root I/O task only | -| `PIO_IOTYPE_NETCDF4C` | netCDF-C/HDF5 | NetCDF-4/HDF5 | No | Yes (level 1) | Sequential with compression | -| `PIO_IOTYPE_NETCDF4P` | netCDF-C/HDF5 | NetCDF-4/HDF5 | Yes | Yes | Parallel HDF5 I/O | - -**Selection criteria:** -- Use `PIO_IOTYPE_PNETCDF` for best parallel performance with classic formats -- Use `PIO_IOTYPE_NETCDF4P` for parallel I/O with compression -- Use `PIO_IOTYPE_NETCDF4C` when compression is critical and parallel I/O not needed -- Use `PIO_IOTYPE_NETCDF` for compatibility/debugging - -## Decompositions and Distributed Arrays - -### Concept - -A decomposition maps a global data array to local subarrays across processors: -- Each processor holds a portion of the global array -- Decomposition defines which global elements each processor owns -- One decomposition per netCDF data type -- Can be saved to/loaded from files - -### Creating a Decomposition - -**C API:** -```c -int ioid; -PIO_Offset *compdof; // 1-based array mapping -int elements_per_pe = DIM_LEN / ntasks; - -// Allocate and fill decomposition map -compdof = malloc(elements_per_pe * sizeof(PIO_Offset)); -for (int i = 0; i < elements_per_pe; i++) - compdof[i] = my_rank * elements_per_pe + i + 1; // 1-based! - -// Initialize decomposition -PIOc_InitDecomp(iosysid, PIO_INT, NDIM, dim_len, - elements_per_pe, compdof, &ioid, - NULL, NULL, NULL); -free(compdof); -``` - -**NetCDF Integration:** -```c -size_t *compdof; // 0-based for netCDF integration -compdof = malloc(elements_per_pe * sizeof(size_t)); -for (int i = 0; i < elements_per_pe; i++) - compdof[i] = my_rank * elements_per_pe + i; // 0-based! - -nc_def_decomp(iosysid, PIO_INT, NDIM2, &dimlen[1], - elements_per_pe, compdof, &ioid, 1, NULL, NULL); -``` - -**Fortran API:** -```fortran -type(io_desc_t) :: iodesc -integer, dimension(:) :: compdof - -! Define mapping (1-based in Fortran) -compdof = my_rank * elements_per_pe + (/ (i, i=1,elements_per_pe) /) - -call PIO_initdecomp(pio_iosystem, PIO_int, dims, compdof, iodesc) -``` - -### Freeing Decompositions - -Always free decomposition resources when done: -```c -// C -PIOc_freedecomp(iosysid, ioid); - -// NetCDF integration -nc_free_decomp(iosysid, ioid); -``` - -```fortran -! Fortran -call PIO_freedecomp(pio_iosystem, iodesc) -``` - -### Saving/Loading Decompositions - -```c -// Save decomposition to file -PIOc_write_nc_decomp(iosysid, filename, cmode, ioid, title, history, fortran_order); - -// Load decomposition from file -PIOc_read_nc_decomp(iosysid, filename, &ioid, comm, PIO_INT, title, history, &fortran_order); -``` - -## Rearrangers - -Rearrangers control how data moves between compute and I/O tasks: - -### BOX Rearranger (`PIO_REARR_BOX`) - -- Arranges data on I/O tasks to be contiguous on disk -- Requires all-to-all communication between compute and I/O tasks -- Better for underlying I/O library performance -- More communication overhead - -**Example:** Global array `0-19` over 5 compute tasks, 2 I/O tasks: -``` -Compute: {0,4,8,12} {16,1,5,9} {13,17,2,6} {10,14,18,3} {7,11,15,19} -I/O: {0,1,2,3,4,5,6,7,8,9} {10,11,12,13,14,15,16,17,18,19} -``` - -### SUBSET Rearranger (`PIO_REARR_SUBSET`) - -- Each I/O task associated with unique subset of compute tasks -- Each compute task sends to exactly one I/O task -- Less communication, but data may be fragmented on I/O tasks -- Usually scales better with task count - -**Example:** Same distribution: -``` -Compute: {0,4,8,12} {16,1,5,9} {13,17,2,6} {10,14,18,3} {7,11,15,19} -I/O: {0,1,4,5,8,9,12,16} {2,3,6,7,10,11,13,14,15,17,18,19} -``` - -**Selection:** SUBSET typically performs better at scale; BOX may be better for small task counts. - -## File Operations - -### Creating Files - -**PIO API:** -```c -// C -int ncid; -PIOc_createfile(iosysid, &ncid, &iotype, filename, PIO_CLOBBER); - -// Define metadata -PIOc_def_dim(ncid, "time", NC_UNLIMITED, &dimid); -PIOc_def_var(ncid, "temperature", PIO_FLOAT, ndims, dimids, &varid); -PIOc_put_att_text(ncid, varid, "units", strlen("K"), "K"); -PIOc_enddef(ncid); -``` - -```fortran -! Fortran -type(file_desc_t) :: pio_file -ret = PIO_createfile(pio_iosystem, pio_file, iotype, filename) -``` - -**NetCDF Integration:** -```c -// Use standard netCDF functions with NC_PIO flag -nc_create(filename, NC_PIO, &ncid); -nc_def_dim(ncid, "time", NC_UNLIMITED, &dimid); -nc_def_var(ncid, "temperature", NC_FLOAT, ndims, dimids, &varid); -``` - -### Opening Files - -```c -// PIO API -PIOc_openfile(iosysid, &ncid, &iotype, filename, PIO_NOWRITE); - -// NetCDF integration -nc_open(filename, NC_PIO, &ncid); -``` - -### Closing Files - -```c -// PIO API -PIOc_closefile(ncid); - -// NetCDF integration -nc_close(ncid); -``` - -## Reading and Writing Distributed Arrays - -### Writing Data - -**PIO API:** -```c -// Set record number (for unlimited dimension) -PIOc_setframe(ncid, varid, record_num); - -// Write distributed array -PIOc_write_darray(ncid, varid, ioid, arraylen, local_data, NULL); -``` - -```fortran -! Fortran -integer, dimension(:) :: local_data -call PIO_write_darray(pio_file, pio_var, iodesc, local_data, ret_val) -``` - -**NetCDF Integration:** -```c -// Write distributed array (record number is parameter) -nc_put_vard_int(ncid, varid, ioid, record_num, local_data); -nc_put_vard_float(ncid, varid, ioid, record_num, local_data); -``` - -### Reading Data - -**PIO API:** -```c -// Set record number -PIOc_setframe(ncid, varid, record_num); - -// Read distributed array -PIOc_read_darray(ncid, varid, ioid, arraylen, local_data); -``` - -```fortran -! Fortran -call PIO_read_darray(pio_file, pio_var, iodesc, local_data, ret_val) -``` - -**NetCDF Integration:** -```c -// Read distributed array -nc_get_vard_int(ncid, varid, ioid, record_num, local_data); -nc_get_vard_float(ncid, varid, ioid, record_num, local_data); -``` - -## Error Handling - -PIO provides three error handling modes: - -| Mode | Behavior | -|------|----------| -| `PIO_INTERNAL_ERROR` | Errors cause abort (default) | -| `PIO_BCAST_ERROR` | Error codes broadcast to all tasks | -| `PIO_RETURN_ERROR` | Errors returned to caller | - -**Setting error handler:** -```c -// Change default before initializing IOsystem -PIOc_set_iosystem_error_handling(PIO_DEFAULT, PIO_RETURN_ERROR, NULL); - -// Change for specific IOsystem -PIOc_set_iosystem_error_handling(iosysid, PIO_RETURN_ERROR, NULL); -``` - -```fortran -! Fortran -call PIO_seterrorhandling(pio_iosystem, PIO_RETURN_ERROR) -call PIO_seterrorhandling(PIO_DEFAULT, PIO_RETURN_ERROR) -``` - -## Finalization - -### Intracomm Mode - -All processors call finalize: -```c -// C -PIOc_finalize(iosysid); - -// NetCDF integration -nc_free_iosystem(iosysid); -``` - -```fortran -! Fortran -call PIO_finalize(pio_iosystem, ierr) -``` - -### Async Mode - -- Compute processors call finalize -- I/O processors receive message and free resources -- When all IOsystems freed, I/O processors exit message loop - -## NetCDF Integration - -### Building with Integration - -```bash -# Autotools -./configure --enable-netcdf-integration --enable-fortran - -# CMake -cmake -DPIO_ENABLE_NETCDF_INTEGRATION=On \ - -DNetCDF_C_PATH=/path/to/netcdf \ - -DPnetCDF_PATH=/path/to/pnetcdf .. -``` - -### Converting Existing NetCDF Code - -**Steps:** -1. Initialize IOsystem with `nc_def_iosystem()` or `nc_def_async()` -2. Use `NC_PIO` flag when creating/opening files -3. Define decomposition(s) with `nc_def_decomp()` -4. Replace `nc_put_vara_*()` with `nc_put_vard_*()` -5. Replace `nc_get_vara_*()` with `nc_get_vard_*()` -6. Free decompositions with `nc_free_decomp()` -7. Free IOsystem with `nc_free_iosystem()` - -**Key functions:** - -| NetCDF Integration | PIO API Equivalent | Purpose | -|-------------------|-------------------|---------| -| `nc_def_iosystem()` | `PIOc_Init_Intracomm()` | Initialize Intracomm mode | -| `nc_def_async()` | `PIOc_init_async()` | Initialize Async mode | -| `nc_def_decomp()` | `PIOc_init_decomp()` | Define decomposition | -| `nc_free_decomp()` | `PIOc_freedecomp()` | Free decomposition | -| `nc_put_vard_*()` | `PIOc_write_darray()` | Write distributed array | -| `nc_get_vard_*()` | `PIOc_read_darray()` | Read distributed array | -| `nc_free_iosystem()` | `PIOc_finalize()` | Free IOsystem | - -### Default IOsystem - -When using netCDF integration, the most recently created IOsystem becomes the default: -```c -// Get current default -int iosysid; -nc_get_iosystem(&iosysid); - -// Set default (for multiple IOsystems) -nc_set_iosystem(iosysid); -``` - -## Common Patterns - -### Basic PIO Workflow - -```c -// 1. Initialize MPI -MPI_Init(&argc, &argv); -MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); -MPI_Comm_size(MPI_COMM_WORLD, &ntasks); - -// 2. Initialize PIO IOsystem -PIOc_Init_Intracomm(MPI_COMM_WORLD, niotasks, stride, - base, PIO_REARR_SUBSET, &iosysid); - -// 3. Create decomposition -PIOc_InitDecomp(iosysid, PIO_FLOAT, ndims, dims, - maplen, compmap, &ioid, NULL, NULL, NULL); - -// 4. Create file and define metadata -PIOc_createfile(iosysid, &ncid, &iotype, filename, PIO_CLOBBER); -PIOc_def_dim(ncid, "x", xdim, &dimids[0]); -PIOc_def_var(ncid, "data", PIO_FLOAT, ndims, dimids, &varid); -PIOc_enddef(ncid); - -// 5. Write data -for (int rec = 0; rec < nrecs; rec++) { - PIOc_setframe(ncid, varid, rec); - PIOc_write_darray(ncid, varid, ioid, maplen, local_data, NULL); -} - -// 6. Close file -PIOc_closefile(ncid); - -// 7. Free decomposition -PIOc_freedecomp(iosysid, ioid); - -// 8. Finalize PIO -PIOc_finalize(iosysid); - -// 9. Finalize MPI -MPI_Finalize(); -``` - -### NetCDF Integration Workflow - -```c -// 1-2. Initialize MPI and IOsystem -MPI_Init(&argc, &argv); -nc_def_iosystem(MPI_COMM_WORLD, 1, 1, 0, 0, &iosysid); - -// 3. Define decomposition -nc_def_decomp(iosysid, PIO_FLOAT, ndims, dims, - maplen, compmap, &ioid, 1, NULL, NULL); - -// 4. Create file with NC_PIO flag -nc_create(filename, NC_PIO, &ncid); -nc_def_dim(ncid, "x", xdim, &dimids[0]); -nc_def_var(ncid, "data", NC_FLOAT, ndims, dimids, &varid); - -// 5. Write distributed data -nc_put_vard_float(ncid, varid, ioid, 0, local_data); - -// 6-8. Cleanup -nc_close(ncid); -nc_free_decomp(iosysid, ioid); -nc_free_iosystem(iosysid); -MPI_Finalize(); -``` - -## Performance Considerations - -### Choosing Number of I/O Tasks - -- **Too few**: I/O becomes bottleneck -- **Too many**: Communication overhead increases -- **Typical**: 1-10% of total tasks for I/O -- **Distribute**: Spread I/O tasks across nodes - -### Rearranger Selection - -- Start with `PIO_REARR_SUBSET` (usually better scaling) -- Try `PIO_REARR_BOX` if I/O performance is poor -- Test both with your specific decomposition - -### IOTYPE Selection - -- `PIO_IOTYPE_PNETCDF`: Best parallel performance for classic formats -- `PIO_IOTYPE_NETCDF4P`: Good parallel performance with compression -- Avoid sequential IOTYPEs (`NETCDF`, `NETCDF4C`) at scale - -### Decomposition Design - -- Balance load across processors -- Minimize fragmentation on I/O tasks -- Consider data access patterns (contiguous vs. strided) -- Reuse decompositions when possible - -## Debugging and Logging - -### Enable Logging - -```c -// Set log level (0=none, 1=errors, 2=warnings, 3=info, 4=debug) -PIOc_set_log_level(3); -``` - -```bash -# Build with logging support -./configure --enable-logging -cmake -DPIO_ENABLE_LOGGING=On -``` - -### Common Issues - -**Decomposition errors:** -- Ensure 1-based indexing for PIO API, 0-based for netCDF integration -- Check that all global array elements are covered -- Verify no overlapping elements between tasks - -**Performance issues:** -- Profile with different I/O task counts -- Test both rearrangers -- Check file system (Lustre striping, etc.) -- Monitor MPI communication patterns - -**Build issues:** -- Ensure all libraries built with same MPI compiler -- Check netCDF built with `--enable-parallel-tests` -- Verify HDF5 built with parallel support - -## Resources - -- **Documentation**: https://ncar.github.io/ParallelIO/ -- **GitHub**: https://github.com/NCAR/ParallelIO -- **Mailing List**: parallelio@googlegroups.com -- **Examples**: `examples/c/` and `examples/f03/` in source tree -- **NetCDF Integration Examples**: `tests/ncint/` and `tests/fncint/` - -## Quick Reference - -### Key Data Types - -**C:** -- `int iosysid` - IOsystem ID -- `int ncid` - File ID -- `int ioid` - Decomposition ID -- `PIO_Offset *compmap` - Decomposition map (1-based) - -**Fortran:** -- `type(iosystem_desc_t)` - IOsystem descriptor -- `type(file_desc_t)` - File descriptor -- `type(var_desc_t)` - Variable descriptor -- `type(io_desc_t)` - Decomposition descriptor - -### Essential Functions - -| Task | C Function | Fortran Subroutine | -|------|-----------|-------------------| -| Init Intracomm | `PIOc_Init_Intracomm()` | `PIO_init()` | -| Init Async | `PIOc_init_async()` | `PIO_init()` | -| Create decomp | `PIOc_InitDecomp()` | `PIO_initdecomp()` | -| Create file | `PIOc_createfile()` | `PIO_createfile()` | -| Write darray | `PIOc_write_darray()` | `PIO_write_darray()` | -| Read darray | `PIOc_read_darray()` | `PIO_read_darray()` | -| Free decomp | `PIOc_freedecomp()` | `PIO_freedecomp()` | -| Finalize | `PIOc_finalize()` | `PIO_finalize()` | From 89d0d063adc2ea6c1085e281a0b4290fdea8fc2a Mon Sep 17 00:00:00 2001 From: James Gallagher Date: Mon, 9 Mar 2026 22:02:30 -0600 Subject: [PATCH 4/4] precommit run --- docs/deep-dive-codex.md | 12 +- gtest-migration-plan.md | 31 +++++ windsurf-harnett/rules/doc-check.md | 2 + windsurf-harnett/rules/local-build-command.md | 12 +- windsurf-harnett/rules/slow-network.md | 7 + windsurf-harnett/skills/linkedin-posts.md | 29 ++++ .../skills/netcdf-architecture/README.md | 2 + .../skills/netcdf-architecture/SKILL.md | 50 +++++-- .../references/COMPONENTS.md | 73 +++++++++- .../references/DATA-STRUCTURES.md | 60 ++++---- .../references/DISPATCH-TABLES.md | 128 ++++++++++++------ .../references/EXAMPLES.md | 48 ++++++- .../references/FORTRAN-INTERFACE.md | 36 +++-- .../references/UDF-PLUGINS.md | 61 +++++---- windsurf-harnett/skills/netcdf-java/skill.md | 34 +++-- windsurf-harnett/skills/opendap/README.md | 1 + windsurf-harnett/skills/opendap/SKILL.md | 42 +++++- .../skills/opendap/references/CLIENT-USAGE.md | 42 +++--- .../skills/opendap/references/CONSTRAINTS.md | 57 ++++++++ .../skills/opendap/references/DATA-MODEL.md | 80 ++++++++--- .../skills/opendap/references/PROTOCOL.md | 57 ++++++-- windsurf-harnett/workflows/implement.md | 8 +- windsurf-harnett/workflows/issue.md | 27 ++++ windsurf-harnett/workflows/release.md | 26 +++- windsurf-harnett/workflows/review.md | 3 +- windsurf-harnett/workflows/roadmap.md | 22 ++- windsurf-harnett/workflows/test.md | 30 +++- 27 files changed, 777 insertions(+), 203 deletions(-) diff --git a/docs/deep-dive-codex.md b/docs/deep-dive-codex.md index 7b76ccc96..1ee0bd728 100644 --- a/docs/deep-dive-codex.md +++ b/docs/deep-dive-codex.md @@ -1,47 +1,57 @@ **Deep Dive Summary** + - This repo is a mature C++ DAP stack with clear layering: core protocol/data model (`libdap`), HTTP/client transport (`libdapclient`), and server filter helpers (`libdapserver`). - It implements both DAP2 and DAP4 in one codebase, with shared type infrastructure and protocol-specific parsers/serializers. - The implementation is production-oriented and heavily tested, but there are a few explicit DAP4 “not yet implemented” paths you should treat as known gaps. **Library Boundaries** + - `libdap` is the core and includes DAP2 + DAP4 model, parsers, marshalling, CE logic, and common utilities: [CMakeLists.txt](/Users/jimg/src/opendap/hyrax_git/libdap4/CMakeLists.txt#L275), [Makefile.am](/Users/jimg/src/opendap/hyrax_git/libdap4/Makefile.am#L64), [libdap.pc.in](/Users/jimg/src/opendap/hyrax_git/libdap4/libdap.pc.in). - `libdapclient` adds client-facing connect/config/HTTP layers: [CMakeLists.txt](/Users/jimg/src/opendap/hyrax_git/libdap4/CMakeLists.txt#L299), [Makefile.am](/Users/jimg/src/opendap/hyrax_git/libdap4/Makefile.am#L71), [libdapclient.pc.in](/Users/jimg/src/opendap/hyrax_git/libdap4/libdapclient.pc.in). - `libdapserver` contains server filter primitives for CGI-style handlers: [CMakeLists.txt](/Users/jimg/src/opendap/hyrax_git/libdap4/CMakeLists.txt#L324), [Makefile.am](/Users/jimg/src/opendap/hyrax_git/libdap4/Makefile.am#L76), [libdapserver.pc.in](/Users/jimg/src/opendap/hyrax_git/libdap4/libdapserver.pc.in). **DAP2 Implementation Path** + - DAP2 client workflow centers on `Connect`: request DAS/DDS/DDX/DataDDS, parse MIME metadata, parse DDS text, then XDR-unmarshal data: [Connect.h](/Users/jimg/src/opendap/hyrax_git/libdap4/Connect.h#L127), [Connect.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/Connect.cc#L71), [Connect.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/Connect.cc#L250). - DAP2 CE selection/function flow is parser-driven (`ce_expr.yy/.lex`) into `ConstraintEvaluator` clauses: [ConstraintEvaluator.h](/Users/jimg/src/opendap/hyrax_git/libdap4/ConstraintEvaluator.h#L41), [ConstraintEvaluator.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/ConstraintEvaluator.cc#L333). - Server-side response shaping is `DODSFilter` + CE + marshalling: [DODSFilter.h](/Users/jimg/src/opendap/hyrax_git/libdap4/DODSFilter.h#L174), [DODSFilter.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/DODSFilter.cc#L89). **DAP4 Implementation Path** + - DAP4 client workflow is `D4Connect`: build DAP4 query keys (`dap4.ce`, `dap4.checksum`), fetch `.dmr` or `.dap`, parse DMR first chunk, then stream-unmarshal payload: [D4Connect.h](/Users/jimg/src/opendap/hyrax_git/libdap4/D4Connect.h#L54), [D4Connect.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/D4Connect.cc#L359), [D4Connect.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/D4Connect.cc#L374). - DMR is the DAP4 root object and supports DAP2↔DAP4 transforms (`build_using_dds`, `getDDS`): [DMR.h](/Users/jimg/src/opendap/hyrax_git/libdap4/DMR.h#L48), [DMR.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/DMR.cc#L156), [DMR.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/DMR.cc#L202). - DMR parsing uses libxml2 SAX with strict/permissive map handling mode: [D4ParserSax2.h](/Users/jimg/src/opendap/hyrax_git/libdap4/D4ParserSax2.h#L75), [D4ParserSax2.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/D4ParserSax2.cc#L310). **Data Model Core** + - `BaseType` is the type-invariant root used across DAP2 and DAP4; it tracks projection state, attrs, parentage, transform hooks: [BaseType.h](/Users/jimg/src/opendap/hyrax_git/libdap4/BaseType.h#L118). - Type enum includes classic DAP2 and DAP4 additions (`Int64`, `UInt64`, `Enum`, `Opaque`, `Group`): [Type.h](/Users/jimg/src/opendap/hyrax_git/libdap4/Type.h#L94). **Transport and I/O** + - HTTP transport is libcurl-based `HTTPConnect`, with DAP header parsing, optional cache integration, cookies/proxy/no_proxy, and C++ stream mode for DAP4: [HTTPConnect.h](/Users/jimg/src/opendap/hyrax_git/libdap4/http_dap/HTTPConnect.h#L52), [HTTPConnect.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/http_dap/HTTPConnect.cc#L557). - DAP4 payload transfer uses chunked framing and receiver-makes-right byte handling: [chunked_stream.h](/Users/jimg/src/opendap/hyrax_git/libdap4/chunked_stream.h#L11), [chunked_istream.h](/Users/jimg/src/opendap/hyrax_git/libdap4/chunked_istream.h#L42), [chunked_istream.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/chunked_istream.cc#L94). **Serialization** + - DAP2 serialization path uses XDR stream/file marshallers: [XDRStreamMarshaller.h](/Users/jimg/src/opendap/hyrax_git/libdap4/XDRStreamMarshaller.h#L53). - DAP4 serialization path uses `D4StreamMarshaller` with optional checksums and stream-first encoding: [D4StreamMarshaller.h](/Users/jimg/src/opendap/hyrax_git/libdap4/D4StreamMarshaller.h#L58), [D4StreamMarshaller.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/D4StreamMarshaller.cc#L118). **Constraint/Function Engines** + - DAP4 CE parser/evaluator (`d4_ce`) supports array slicing, shared-dim behavior, map pruning, and filter clauses; includes XXS-aware error redaction: [D4ConstraintEvaluator.h](/Users/jimg/src/opendap/hyrax_git/libdap4/d4_ce/D4ConstraintEvaluator.h#L48), [D4ConstraintEvaluator.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/d4_ce/D4ConstraintEvaluator.cc#L42), [D4ConstraintEvaluator.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/d4_ce/D4ConstraintEvaluator.cc#L181). - Server function registry is a singleton (`ServerFunctionsList`) used by DAP2 and DAP4 function paths: [ServerFunctionsList.h](/Users/jimg/src/opendap/hyrax_git/libdap4/ServerFunctionsList.h#L46), [ServerFunctionsList.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/ServerFunctionsList.cc#L65), [D4FunctionEvaluator.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/d4_function/D4FunctionEvaluator.cc#L104). **Tooling and Quality** + - Scale: ~207 `.cc`, ~234 `.h`, 7 parser grammars, 7 lexer files. - Strong testing surface: autotest integration suites and ~60 CppUnit-style unit tests, including DAP4 round-trips and DMR↔DAP2 translation: [tests/Makefile.am](/Users/jimg/src/opendap/hyrax_git/libdap4/tests/Makefile.am#L27), [unit-tests/Makefile.am](/Users/jimg/src/opendap/hyrax_git/libdap4/unit-tests/Makefile.am#L83). - Pre-commit and CI hooks are in place for formatting/lint sanity: [pre-commit.yml](/Users/jimg/src/opendap/hyrax_git/libdap4/.github/workflows/pre-commit.yml#L1), [.pre-commit-config.yaml](/Users/jimg/src/opendap/hyrax_git/libdap4/.pre-commit-config.yaml#L1). **Notable Gaps/Risks** + - DAP4 error-body handling is explicitly unfinished in client paths (`FIXME`/throw): [D4Connect.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/D4Connect.cc#L131), [D4Connect.cc](/Users/jimg/src/opendap/hyrax_git/libdap4/D4Connect.cc#L404). - Some legacy/deprecated config/proxy paths remain and increase maintenance surface: [RCReader.h](/Users/jimg/src/opendap/hyrax_git/libdap4/RCReader.h#L93), [README.dodsrc](/Users/jimg/src/opendap/hyrax_git/libdap4/README.dodsrc). - Mixed old/new build systems (Autotools + CMake) are both supported; good for portability, but raises drift risk: [Makefile.am](/Users/jimg/src/opendap/hyrax_git/libdap4/Makefile.am#L1), [CMakeLists.txt](/Users/jimg/src/opendap/hyrax_git/libdap4/CMakeLists.txt#L1). -If you want, I can do a second-pass deep dive focused only on one of these: `libdap` internals, `libdapclient` transport/cache behavior, or `libdapserver` handler integration patterns. \ No newline at end of file +If you want, I can do a second-pass deep dive focused only on one of these: `libdap` internals, `libdapclient` transport/cache behavior, or `libdapserver` handler integration patterns. diff --git a/gtest-migration-plan.md b/gtest-migration-plan.md index 92f4cffce..c86020254 100644 --- a/gtest-migration-plan.md +++ b/gtest-migration-plan.md @@ -5,15 +5,18 @@ This plan covers only the CppUnit-based tests that are built and run through the autotools `make check` path. Included: + - `unit-tests/Makefile.am` - `http_dap/unit-tests/Makefile.am` - `d4_ce/unit-tests/Makefile.am` Excluded: + - CMake test wiring - The autotest integration suites in `tests/Makefile.am`, except for shared test support such as `libtest-types.a` The current autotools CppUnit surface is 63 test binaries: + - 59 in `unit-tests/` - 3 in `http_dap/unit-tests/` - 1 in `d4_ce/unit-tests/` @@ -67,6 +70,7 @@ Use one consistent conversion pattern across the tree: - `setUp()` / `tearDown()` to `SetUp()` / `TearDown()` Preserve test logic during the framework switch: + - keep fixture setup and cleanup behavior unchanged - keep environment-variable setup unchanged - keep test asset paths and generated `test_config.h` usage unchanged @@ -79,16 +83,19 @@ The batches are ordered to establish the GoogleTest pattern on low-risk tests fi ### Batch 0: Build Skeleton And Proof Of Pattern Purpose: + - land the autotools GoogleTest dependency wiring - add the shared runner/helper - prove the conversion style in one test per subtree Tests: + - `unit-tests/BaseTypeTest` - `d4_ce/unit-tests/D4ConstraintEvaluatorTest` - `http_dap/unit-tests/HTTPConnectTest` Exit criteria: + - `autoreconf -fi` - `./configure` - each converted binary builds and runs under `make check` @@ -96,10 +103,12 @@ Exit criteria: ### Batch 1: Scalar And Utility Core Tests Purpose: + - convert low-coupling tests with straightforward fixtures and assertions - validate the assertion mapping and runner helper Tests: + - `unit-tests/RegexTest` - `unit-tests/ByteTest` - `unit-tests/MIMEUtilTest` @@ -118,6 +127,7 @@ Tests: - `unit-tests/Float64Test` Why this batch: + - mostly local assertions - little shared state - low filesystem and network sensitivity @@ -125,9 +135,11 @@ Why this batch: ### Batch 2: Core DAP2 Model And Container Tests Purpose: + - convert the core object-model tests that exercise the library boundaries described in `docs/deep-dive-codex.md` Tests: + - `unit-tests/ArrayTest` - `unit-tests/GridTest` - `unit-tests/AttrTableTest` @@ -141,15 +153,18 @@ Tests: - `unit-tests/BaseTypeTest` Why this batch: + - still mostly in-process - builds confidence in fixture conversion before older legacy-style tests are touched ### Batch 3: Legacy CppUnit Pattern Tests Purpose: + - convert the older tests that still use the historic `*T` naming and older fixture style Tests: + - `unit-tests/marshT` - `unit-tests/arrayT` - `unit-tests/attrTableT` @@ -161,15 +176,18 @@ Tests: - `unit-tests/util_mitTest` Why this batch: + - these are likely to require the most mechanical cleanup - separating them avoids slowing down the cleaner modern conversions ### Batch 4: Parser, XML, And Translation Tests Purpose: + - migrate the tests that depend on XML parsing, generated config, and DAP2/DAP4 translation paths Tests: + - `unit-tests/DDXParserTest` - `unit-tests/D4ParserSax2Test` - `unit-tests/DMRTest` @@ -186,15 +204,18 @@ Tests: - `unit-tests/D4AsyncDocTest` Why this batch: + - these tests are tightly tied to the DAP4 parsing and translation flows - they are a good midpoint between simple unit tests and stream/cache tests ### Batch 5: Marshaller, Stream, And Concurrency Tests Purpose: + - convert tests that exercise serialization, streaming, and threading behavior Tests: + - `unit-tests/MarshallerTest` - `unit-tests/MarshallerFutureTest` - `unit-tests/MarshallerThreadTest` @@ -204,46 +225,56 @@ Tests: - `unit-tests/chunked_iostream_test` Why this batch: + - more sensitive to fatal vs non-fatal assertions - often easier to debug after the general fixture strategy is already proven ### Batch 6: Cache And Local Filesystem Tests Purpose: + - convert tests that create, clean, or inspect local cache state and generated files Tests: + - `unit-tests/RCReaderTest` - `unit-tests/DAPCache3Test` - `unit-tests/ResponseCacheTest` if re-enabled for autotools - `http_dap/unit-tests/HTTPCacheTest` Why this batch: + - fixture cleanup matters - stale temp files and cache locks can hide migration bugs ### Batch 7: HTTP And External-Environment Tests Purpose: + - convert the most environment-sensitive tests last Tests: + - `http_dap/unit-tests/HTTPConnectTest` - `http_dap/unit-tests/HTTPThreadsConnectTest` Why this batch: + - network behavior and remote state can obscure framework migration issues - these should be used only after the helper, fixture, and assertion patterns are stable ### Batch 8: Optional Resource-Heavy Test Purpose: + - convert the largest and least convenient test only after the main migration is complete Tests: + - `unit-tests/BigArrayTest` Why this batch: + - already optional under autotools - high runtime and resource cost - poor candidate for early validation diff --git a/windsurf-harnett/rules/doc-check.md b/windsurf-harnett/rules/doc-check.md index 57395d2bb..f79a9c292 100644 --- a/windsurf-harnett/rules/doc-check.md +++ b/windsurf-harnett/rules/doc-check.md @@ -3,7 +3,9 @@ trigger: always_on --- # Documentation Check Rule + Before planning any code changes: + 1. **Review Architecture & Design**: Check [docs/design.md](../../../docs/design.md) for system architecture, component interactions, and technical specifications. 2. **Verify Requirements**: Consult [docs/prd.md](../../../docs/prd.md) to ensure changes align with product requirements, API specifications, and feature definitions. 3. **Understand User Impact**: Read [docs/prfaq.md](../../../docs/prfaq.md) to consider how changes affect users, compatibility, and use cases. diff --git a/windsurf-harnett/rules/local-build-command.md b/windsurf-harnett/rules/local-build-command.md index df75022fb..04c8f7c4f 100644 --- a/windsurf-harnett/rules/local-build-command.md +++ b/windsurf-harnett/rules/local-build-command.md @@ -1,13 +1,16 @@ --- trigger: model_decision --- + # Local Build Commands for NEP ## When to Use This Rule -Use these paths for **local development builds on Ed's machine**. + +Use these paths for **local development builds on Ed's machine**. For CI/GitHub Actions, different paths are used (see `.github/workflows/`). ## Machine-Specific Dependency Paths + - **HDF5**: `/usr/local/hdf5-2.0.0/` - **NetCDF-C**: `/usr/local/netcdf-c-4.10.0/` - **NetCDF-Fortran**: `/usr/local/netcdf-fortran/` (if Fortran enabled) @@ -15,7 +18,9 @@ For CI/GitHub Actions, different paths are used (see `.github/workflows/`). - **GeoTIFF**: System packages (`libgeotiff-dev`, `libtiff-dev`) ## Runtime Environment + Before running tests or executables: + ```bash export LD_LIBRARY_PATH=/usr/local/hdf5-2.0.0/lib:/usr/local/netcdf-c-4.10.0/lib:/usr/local/netcdf-fortran/lib:/usr/local/cdf-3.9.1/lib:$LD_LIBRARY_PATH ``` @@ -23,9 +28,11 @@ export LD_LIBRARY_PATH=/usr/local/hdf5-2.0.0/lib:/usr/local/netcdf-c-4.10.0/lib: ## Build System Options ### Autotools (Primary) + Working directory: `/home/ed/NEP` **Common configure flags:** + - `--enable-geotiff` - Enable GeoTIFF reader - `--enable-cdf` - Enable NASA CDF reader - `--disable-lz4` - Disable LZ4 compression @@ -34,6 +41,7 @@ Working directory: `/home/ed/NEP` - `--disable-shared` - Build static libraries only **Full build command:** + ```bash autoreconf -i && \ CFLAGS="-g -O0" \ @@ -44,6 +52,7 @@ make clean && make -j$(nproc) && make check ``` ### CMake (Alternative) + **IMPORTANT**: All CMake builds must use the `build` directory, which is git-ignored. Working directory: `/home/ed/NEP` @@ -62,6 +71,7 @@ make -j$(nproc) && ctest **Never create CMake build artifacts outside the `build` directory** to avoid cluttering the repository with untracked files. ## Troubleshooting + - **"library not found" errors**: Check `LD_LIBRARY_PATH` is set - **"header not found" errors**: Verify `CPPFLAGS` includes correct paths - **Link errors**: Ensure `LDFLAGS` includes all dependency lib directories diff --git a/windsurf-harnett/rules/slow-network.md b/windsurf-harnett/rules/slow-network.md index 0bb194941..752ccfcca 100644 --- a/windsurf-harnett/rules/slow-network.md +++ b/windsurf-harnett/rules/slow-network.md @@ -7,39 +7,46 @@ trigger: always_on The network here is slow and flakey. Follow these specific retry strategies: ## HTTP Requests (read_url_content, search_web) + - **Retry count**: 3 attempts - **Timeout**: 10 seconds per attempt - **Backoff**: 5 seconds between retries - **Pre-check**: Use `curl -I` for connectivity before full requests ## MCP Server Calls + - **Retry count**: 5 attempts - **Delay**: 2 seconds wait between attempts - **Timeout**: 30 seconds per call - **Pre-check**: Verify MCP server process is running ## Git Operations + - **Retry count**: Immediate retry once - **Issue**: Distinguish network vs authentication failures - **Auth failures**: Don't retry - check credentials ## Diagnostic Commands + - **Basic connectivity**: `ping -c 1 8.8.8.8` - **HTTP test**: `curl -I https://github.com` - **MCP status**: Check process list for MCP server ## Failure Type Handling + - **Timeouts**: Retry with exponential backoff - **Connection refused**: Check if service is running - **Authentication errors**: Don't retry - fix credentials first - **DNS failures**: Check `/etc/resolv.conf` and retry ## Tool-Specific Guidance + - **bash network commands**: Always test connectivity first - **file operations**: Local only - no retries needed - **build commands**: Network-dependent parts need retry logic ## GitHub Interactions + - **Prefer GitHub CLI**: Use `gh` command line tool instead of MCP GitHub tools - **MCP GitHub tools**: Unreliable due to TLS handshake timeouts on slow network - **Issue creation**: Use `gh issue create --title "..." --body "..."` or `gh issue create --body-file ` diff --git a/windsurf-harnett/skills/linkedin-posts.md b/windsurf-harnett/skills/linkedin-posts.md index 50f2e4a08..1a4889a31 100644 --- a/windsurf-harnett/skills/linkedin-posts.md +++ b/windsurf-harnett/skills/linkedin-posts.md @@ -7,12 +7,14 @@ description: Best practices for creating engaging and effective LinkedIn posts ## My Audience Profile **Target Audience:** + - Engineers (software, systems, data) - Earth scientists (geoscientists, climate scientists, environmental scientists) - AI/ML engineers - Scientific researchers and technical professionals **Content Approach:** + - Focus on the work and its benefits, not self-promotion - Scientific community values substance over personal branding - Emphasize technical contributions, research findings, and practical applications @@ -24,12 +26,14 @@ description: Best practices for creating engaging and effective LinkedIn posts ## Content Strategy ### Know Your Audience + - Tailor content to resonate with your professional network - Understand demographic characteristics (age, gender, location, education, income) - Understand psychographic characteristics (personality, values, interests, lifestyle, motivations) - Address topics relevant to your industry and audience's pain points ### Content Types That Perform Well + - **Personal stories** of triumph and professional challenges - **Educational content** - how-to guides, tips, industry insights - **Case studies and research** - builds credibility and trust @@ -41,6 +45,7 @@ description: Best practices for creating engaging and effective LinkedIn posts ## Writing Structure ### Craft a Compelling Hook (First 2 Seconds Count) + - Start with attention-grabbing first sentence - Use statistics, quotes, questions, or personal anecdotes - Create intrigue to make readers click "See more" @@ -48,6 +53,7 @@ description: Best practices for creating engaging and effective LinkedIn posts - Spend 50% of your time on the headline alone ### Hook Techniques That Work + - Little-known facts - Behind-the-scenes insights - Catchy quotes @@ -58,6 +64,7 @@ description: Best practices for creating engaging and effective LinkedIn posts - How-to offerings ### Format for Readability + - **One post = one thesis** - stay focused - Break walls of text into single-sentence paragraphs - Use 3-4 hard paragraph breaks after headline to create intrigue @@ -68,6 +75,7 @@ description: Best practices for creating engaging and effective LinkedIn posts - Leave white space between sections ### Length Guidelines + - Keep it concise but impactful - every sentence must add value - Longer posts (up to 15 lines) are acceptable on LinkedIn - Only first 3 lines visible initially - use as teaser @@ -76,6 +84,7 @@ description: Best practices for creating engaging and effective LinkedIn posts ## Engagement Tactics ### Call-to-Action (CTA) + - Always include a clear CTA - Tell audience exactly what to do next - Options: comment, like, share, click link, answer question @@ -83,6 +92,7 @@ description: Best practices for creating engaging and effective LinkedIn posts - Posts with engagement get prioritized by LinkedIn algorithm ### Ask Questions + - End posts with questions to encourage commenting - LinkedIn rewards posts with comments - More comments = higher chance of trending @@ -90,6 +100,7 @@ description: Best practices for creating engaging and effective LinkedIn posts - Question ideas: productivity tools, favorite quotes, career advice, industry tips ### Tag and Mention + - @mention relevant connections or influencers - Tag people who contributed to your story - When they engage, post reaches their network @@ -99,6 +110,7 @@ description: Best practices for creating engaging and effective LinkedIn posts ## Visual Elements ### When to Use Visuals + - Text-only posts often perform best on LinkedIn - Add visuals when they enhance understanding - Use charts/diagrams for data, trends, case studies @@ -107,6 +119,7 @@ description: Best practices for creating engaging and effective LinkedIn posts - Professional-looking visuals only ### Video Content + - Videos are 20 times more shareable than other formats - Keep videos short and professional - Optimize for mobile viewing @@ -120,6 +133,7 @@ description: Best practices for creating engaging and effective LinkedIn posts ## Hashtag Strategy ### Best Practices + - Use 3-5 relevant hashtags maximum - Place hashtags at the end of post - Mix niche and well-known hashtags @@ -128,6 +142,7 @@ description: Best practices for creating engaging and effective LinkedIn posts - Research hashtags relevant to your topics ### Popular Hashtags + - #entrepreneurship, #startups, #smallbusiness - #marketing, #digitalmarketing, #branding - #productivity, #motivation, #strategy @@ -137,6 +152,7 @@ description: Best practices for creating engaging and effective LinkedIn posts ## Critical Don'ts ### External Links + - **Never include external links in the post body** - LinkedIn punishes posts with external links (low engagement) - Instead: mention link is in comments section @@ -144,6 +160,7 @@ description: Best practices for creating engaging and effective LinkedIn posts - LinkedIn wants users to stay on platform ### Other Mistakes to Avoid + - Don't use hashtags in headlines - Don't overuse emojis (few strategic ones only) - Don't bore your audience @@ -153,6 +170,7 @@ description: Best practices for creating engaging and effective LinkedIn posts ## Content Principles ### Provide Value + - Share actionable insights, tips, strategies - Offer industry-specific knowledge - Provide time-saving hacks @@ -160,6 +178,7 @@ description: Best practices for creating engaging and effective LinkedIn posts - Position yourself as valuable resource ### Be Authentic + - Share genuine experiences - successes and failures - Be relatable and human - Show personality (don't be afraid to be silly) @@ -168,6 +187,7 @@ description: Best practices for creating engaging and effective LinkedIn posts - Human-to-human marketing resonates ### Tell Stories + - Every story needs: problem, solution, moral - Stories trigger emotions and sell - Create sense of kinship and relatability @@ -176,6 +196,7 @@ description: Best practices for creating engaging and effective LinkedIn posts - "Infotainment" - inform and entertain ### Stay Timely and Relevant + - Discuss current events impacting your field - Share insights on new technologies - Comment on emerging trends @@ -185,12 +206,14 @@ description: Best practices for creating engaging and effective LinkedIn posts ## Posting Strategy ### Timing and Consistency + - Post at the same time every day - Consistency helps connections know when to expect content - Schedule posts for maximum visibility - Best times vary by audience - test and track ### Experiment and Adapt + - Try different formats (lists, quotes, infographics, polls) - Test what resonates with your audience - Track which formats get most interaction @@ -198,6 +221,7 @@ description: Best practices for creating engaging and effective LinkedIn posts - Don't delete underperforming posts - learn from them ### Pre-Publish Checklist + - Review for grammatical errors - Verify content relevance to professional goals - Confirm message aligns with intended tone @@ -208,6 +232,7 @@ description: Best practices for creating engaging and effective LinkedIn posts ## Advanced Tactics ### Offer Intellectual Property (IP) + - Share valuable resources with your community - Examples: checklists, templates, how-to guides, scripts - Based on your professional experience @@ -215,12 +240,14 @@ description: Best practices for creating engaging and effective LinkedIn posts - Builds trust and authority ### Use LinkedIn Features + - Polls for engagement - Native video uploads (not external links) - Document uploads for valuable resources - LinkedIn articles for longer content ### Algorithm Optimization + - Posts with good engagement get prioritized - Comments matter more than likes - Early engagement (first hour) is critical @@ -229,6 +256,7 @@ description: Best practices for creating engaging and effective LinkedIn posts - Plain text posts often outperform media posts ## Key Metrics to Track + - Views and impressions - Comments (most valuable) - Likes and reactions @@ -238,6 +266,7 @@ description: Best practices for creating engaging and effective LinkedIn posts - Connection requests from posts ## Remember + - LinkedIn users come to grow professionally, not kill time - Focus on educational, informative, relevant content - Build genuine relationships through two-way conversations diff --git a/windsurf-harnett/skills/netcdf-architecture/README.md b/windsurf-harnett/skills/netcdf-architecture/README.md index 1fd153250..716f6c965 100644 --- a/windsurf-harnett/skills/netcdf-architecture/README.md +++ b/windsurf-harnett/skills/netcdf-architecture/README.md @@ -20,6 +20,7 @@ This Windsurf skill provides comprehensive knowledge of the NetCDF-C library arc ## When to Use Use this skill when: + - Adding new features to NetCDF-C - Debugging format-specific issues - Understanding data flow through the library @@ -49,6 +50,7 @@ To use this skill in Windsurf: ## Maintenance Update this skill when: + - New dispatch tables are added - Major architectural changes occur - New storage backends are implemented diff --git a/windsurf-harnett/skills/netcdf-architecture/SKILL.md b/windsurf-harnett/skills/netcdf-architecture/SKILL.md index 0d06cc280..28dd270f7 100644 --- a/windsurf-harnett/skills/netcdf-architecture/SKILL.md +++ b/windsurf-harnett/skills/netcdf-architecture/SKILL.md @@ -3,8 +3,8 @@ name: netcdf-architecture description: Understanding the NetCDF-C library architecture including dispatch tables, format implementations (NetCDF-3, HDF5, Zarr, DAP), I/O layers, and metadata structures. Use when working on NetCDF-C codebase, debugging format issues, adding new features, or understanding how different storage backends interact. metadata: author: netcdf-analysis - version: "1.0" - date: "2026-01-14" + version: '1.0' + date: '2026-01-14' --- # NetCDF-C Architecture Skill @@ -28,21 +28,21 @@ Every file format implements the same `NC_Dispatch` interface containing ~70 fun struct NC_Dispatch { int model; // Format identifier int dispatch_version; // Compatibility version - + // File operations int (*create)(...); int (*open)(...); int (*close)(...); - + // Variable I/O int (*get_vara)(...); int (*put_vara)(...); - + // Metadata operations int (*def_dim)(...); int (*def_var)(...); int (*put_att)(...); - + // ... ~60 more function pointers }; ``` @@ -94,6 +94,7 @@ typedef struct NC { **Purpose**: Provides unified API facade and routes calls to appropriate format implementations. **Critical Files**: + - `ddispatch.c` - Dispatch initialization, global state management - `dfile.c` - File open/create orchestration, format detection - `dvarget.c`, `dvarput.c` - Variable I/O entry points @@ -101,12 +102,14 @@ typedef struct NC { - `dinfermodel.c` - Format detection (magic numbers, URLs) **Format Detection Logic**: + 1. Check magic number (first 8 bytes) - includes user-defined magic numbers 2. Parse URL scheme (http://, s3://, file://) 3. Analyze mode flags (NC_NETCDF4, NC_CLASSIC_MODEL, NC_UDF0-NC_UDF9, etc.) 4. Select appropriate dispatch table (built-in or user-defined) **Utilities**: + - `ncjson.c` - JSON parsing - `ncuri.c` - URI parsing - `dauth.c` - Authentication (includes RC file parsing for UDF configuration) @@ -124,6 +127,7 @@ typedef struct NC { **Metadata Structure**: `NC3_INFO` - Simple arrays with hashmaps **Critical Files**: + - `nc3dispatch.c` (517 lines) - Dispatch table implementation - `nc3internal.c` - Metadata management - `ncx.c` (743KB) - XDR-like encoding/decoding for all data types @@ -132,12 +136,14 @@ typedef struct NC { - `var.c`, `dim.c` - Variable and dimension management **I/O Abstraction (ncio layer)**: + - `posixio.c` - Standard POSIX file I/O - `memio.c` - In-memory files - `httpio.c` - HTTP byte-range access - `s3io.c` - S3 object storage **Data Structures**: + ```c typedef struct NC3_INFO { NC_dimarray dims; // Dimensions @@ -158,6 +164,7 @@ typedef struct NC3_INFO { **Note**: This is NOT a complete implementation - it delegates to HDF5 or Zarr backends. **Files**: + - `nc4dispatch.c` - Minimal initialization - `nc4attr.c`, `nc4dim.c`, `nc4var.c` - Enhanced metadata operations - `nc4grp.c` - Group operations @@ -173,6 +180,7 @@ typedef struct NC3_INFO { **Metadata Structure**: `NC_FILE_INFO_T` with hierarchical groups **Critical Files**: + - `hdf5dispatch.c` (152 lines) - Dispatch table - `nc4hdf.c` (87KB) - Core HDF5 integration - `hdf5open.c` (99KB) - File opening, metadata reading from HDF5 @@ -182,6 +190,7 @@ typedef struct NC3_INFO { - `H5FDhttp.c` - HTTP virtual file driver for byte-range access **Key Data Structures**: + ```c typedef struct NC_FILE_INFO_T { NC_GRP_INFO_T* root_grp; // Root group @@ -214,6 +223,7 @@ typedef struct NC_VAR_INFO_T { **Metadata Structure**: `NC_FILE_INFO_T` (same as HDF5) **Critical Files**: + - `zdispatch.c` (323 lines) - Dispatch table - `zarr.c` - Main Zarr implementation - `zsync.c` (84KB) - Data synchronization, chunk management @@ -222,6 +232,7 @@ typedef struct NC_VAR_INFO_T { - `zxcache.c` - Chunk caching **Storage Abstraction (zmap)**: + - `zmap.c` - Abstract storage interface - `zmap_file.c` - Filesystem backend - `zmap_s3sdk.c` - AWS S3 backend @@ -236,12 +247,14 @@ typedef struct NC_VAR_INFO_T { **Dispatch Table**: `NCD2_dispatcher` in `ncd2dispatch.c` **Components**: + - `ncd2dispatch.c` (85KB) - Dispatch implementation - `getvara.c` (44KB) - Maps NetCDF API to DAP requests - `constraints.c` - DAP constraint expression handling - `cache.c` - Response caching **OC2 Library** (OPeNDAP Client in `oc2/`): + - `oc.c` (62KB) - Main client implementation - `dapparse.c`, `daplex.c` - DDS/DAS parsing - `ocdata.c` - Data retrieval and decoding @@ -254,6 +267,7 @@ typedef struct NC_VAR_INFO_T { **Dispatch Table**: `NCD4_dispatcher` in `ncd4dispatch.c` **Critical Files**: + - `ncd4dispatch.c` (24KB) - Dispatch table - `d4parser.c` (49KB) - DMR (Dataset Metadata Response) parsing - `d4data.c` - Binary data handling @@ -270,6 +284,7 @@ typedef struct NC_VAR_INFO_T { **Dispatch Tables**: Registered via `nc_def_user_format()` or RC file configuration **Key Features**: + - **Plugin loading**: Automatic loading from RC files during `nc_initialize()` - **Magic number detection**: Optional automatic format detection - **Shared libraries**: .so (Unix) or .dll (Windows) plugins @@ -284,6 +299,7 @@ typedef struct NC_VAR_INFO_T { **Registration Methods**: **Programmatic Registration**: + ```c // Register UDF in slot 0 with magic number nc_def_user_format(NC_UDF0 | NC_NETCDF4, &my_dispatcher, "MYFORMAT"); @@ -294,6 +310,7 @@ nc_inq_user_format(NC_UDF0, &disp, magic_buffer); ``` **RC File Configuration** (`.ncrc`): + ```ini NETCDF.UDF0.LIBRARY=/usr/local/lib/libmyformat.so NETCDF.UDF0.INIT=myformat_init @@ -301,6 +318,7 @@ NETCDF.UDF0.MAGIC=MYFORMAT ``` **Plugin Loading Process**: + 1. RC files parsed during `nc_initialize()` 2. Library loaded via `dlopen()` (Unix) or `LoadLibrary()` (Windows) 3. Init function located via `dlsym()` or `GetProcAddress()` @@ -309,6 +327,7 @@ NETCDF.UDF0.MAGIC=MYFORMAT 6. Plugin remains loaded for process lifetime **RC File Search Order**: + 1. `$HOME/.ncrc` 2. `$HOME/.daprc` 3. `$HOME/.dodsrc` @@ -317,10 +336,12 @@ NETCDF.UDF0.MAGIC=MYFORMAT 6. `$CWD/.dodsrc` **UDF Slot Modes**: + - **UDF0, UDF1**: Original slots, mode flags in lower 16 bits - **UDF2-UDF9**: Extended slots, mode flags in upper 16 bits **Pre-defined Dispatch Functions** (for plugin use): + - `NC_RO_*` - Read-only stubs (return `NC_EPERM`) - `NC_NOTNC4_*` - Not-NetCDF-4 stubs (return `NC_ENOTNC4`) - `NC_NOTNC3_*` - Not-NetCDF-3 stubs (return `NC_ENOTNC3`) @@ -329,6 +350,7 @@ NETCDF.UDF0.MAGIC=MYFORMAT - `NC4_*` - NetCDF-4 inquiry functions using internal metadata model **Critical Files**: + - `libdispatch/dfile.c` - UDF dispatch table storage (`UDF0_dispatch_table`, etc.) - `libdispatch/ddispatch.c` - `nc_def_user_format()`, `nc_inq_user_format()` - `libdispatch/drc.c` - RC file parsing for UDF configuration @@ -338,13 +360,14 @@ NETCDF.UDF0.MAGIC=MYFORMAT - `libdispatch/dnotnc*.c` - Pre-defined not-supported stubs **Example Plugin Structure**: + ```c #include "netcdf_dispatch.h" static NC_Dispatch my_dispatcher = { NC_FORMATX_UDF0, // Use UDF slot 0 NC_DISPATCH_VERSION, // Current ABI version - + NC_RO_create, // Read-only: use predefined function my_open, // Custom open function my_close, // Custom close function @@ -354,19 +377,21 @@ static NC_Dispatch my_dispatcher = { // Initialization function - must be exported int my_plugin_init(void) { - return nc_def_user_format(NC_UDF0 | NC_NETCDF4, - &my_dispatcher, + return nc_def_user_format(NC_UDF0 | NC_NETCDF4, + &my_dispatcher, "MYFMT"); } ``` **Security Considerations**: + - RC files must specify absolute library paths - Plugins execute arbitrary code in process space - Only load trusted libraries - Library verifies dispatch table ABI version **Common Use Cases**: + - Proprietary or specialized file formats - Custom storage backends - Format translation layers @@ -417,18 +442,21 @@ Return ncid to user ### 3. Metadata Access All formats use indexed structures for fast lookup: + - **NC3**: Arrays with `NC_hashmap` - **NC4/HDF5/Zarr**: `NCindex` (hash-based index) ## Important Headers ### Public API + - `netcdf.h` - Main public API - `netcdf_par.h` - Parallel I/O extensions - `netcdf_filter.h` - Filter API - `netcdf_mem.h` - In-memory file API ### Internal Interfaces + - `ncdispatch.h` - Dispatch layer interfaces - `netcdf_dispatch.h` - NC_Dispatch structure definition - `nc.h` - NC structure and common functions @@ -439,6 +467,7 @@ All formats use indexed structures for fast lookup: ## When to Use This Skill Use this skill when: + - **Adding new features** to NetCDF-C - **Debugging format-specific issues** (e.g., HDF5 vs Zarr differences) - **Understanding data flow** through the library @@ -465,18 +494,21 @@ Use this skill when: ### Common Tasks **Adding a new API function**: + 1. Add to `include/netcdf.h` 2. Add entry point in `libdispatch/` 3. Add to `NC_Dispatch` structure 4. Implement in each format's dispatch table **Adding a new format**: + 1. Create new library directory 2. Implement `NC_Dispatch` table 3. Register in `libdispatch/ddispatch.c` 4. Add format detection logic **Debugging I/O issues**: + 1. Enable logging: `export NETCDF_LOG_LEVEL=5` 2. Check dispatch table selection 3. Trace through format-specific implementation diff --git a/windsurf-harnett/skills/netcdf-architecture/references/COMPONENTS.md b/windsurf-harnett/skills/netcdf-architecture/references/COMPONENTS.md index 564e65779..b7ed6dd43 100644 --- a/windsurf-harnett/skills/netcdf-architecture/references/COMPONENTS.md +++ b/windsurf-harnett/skills/netcdf-architecture/references/COMPONENTS.md @@ -7,6 +7,7 @@ This reference provides detailed information about each major component in the N ### Core Files **ddispatch.c** (477 lines) + - Global state management (`NCglobalstate`) - Dispatch initialization (`NCDISPATCH_initialize()`, `NCDISPATCH_finalize()`) - Atomic type utilities @@ -14,24 +15,28 @@ This reference provides detailed information about each major component in the N - Manages: temp directories, home directory, RC files, chunk cache defaults **dfile.c** (2225 lines) + - File open/create orchestration - User-defined format registration (`nc_def_user_format()`) - Format detection coordination - NC structure lifecycle management **dinfermodel.c** (46KB) + - Format detection from magic numbers - URL scheme parsing (http://, https://, s3://, file://) - Mode flag analysis - Dispatch table selection logic **dvarget.c / dvarput.c** + - Variable I/O entry points - Type conversion coordination - Stride/index calculation - Delegates to format-specific `get_vara()` / `put_vara()` **dvar.c, datt.c, ddim.c, dgroup.c, dtype.c** + - Metadata operation entry points - Validation and error checking - Delegation to format-specific implementations @@ -51,6 +56,7 @@ This reference provides detailed information about each major component in the N ## libsrc/ - Classic NetCDF-3 ### Format Support + - CDF-1: Classic format (32-bit offsets) - CDF-2: 64-bit offset format - CDF-5: 64-bit data format (large variables) @@ -58,18 +64,21 @@ This reference provides detailed information about each major component in the N ### Core Implementation **nc3dispatch.c** (517 lines) + - `NC3_dispatcher` table with ~70 function pointers - Implements all required dispatch operations - Returns `NC_ENOTNC4` for NetCDF-4-only features - Stubs for groups, user-defined types, compression **nc3internal.c** (1784 lines) + - `NC3_INFO` structure management - Metadata lifecycle (create, duplicate, free) - Type checking (`nc3_cktype()`) - Format version handling **ncx.c** (743KB - generated from ncx.m4) + - External Data Representation (XDR-like) - Encoding/decoding for all atomic types - Byte swapping for endianness @@ -77,6 +86,7 @@ This reference provides detailed information about each major component in the N - Platform-specific optimizations **putget.c** (353KB - generated from putget.m4) + - Variable data I/O operations - Type conversion matrix (all type combinations) - Strided access implementation @@ -84,18 +94,21 @@ This reference provides detailed information about each major component in the N - Fill value management **attr.c** (47KB - generated from attr.m4) + - Attribute CRUD operations - Attribute type conversion - Global vs variable attributes - Attribute renaming and deletion **var.c** (18KB) + - Variable definition and inquiry - Variable renaming - Coordinate variable detection - Record variable management **dim.c** (10KB) + - Dimension definition and inquiry - Unlimited dimension handling - Dimension renaming @@ -103,27 +116,32 @@ This reference provides detailed information about each major component in the N ### I/O Layer (ncio abstraction) **ncio.h / ncio.c** + - Abstract I/O interface - Strategy pattern for different backends - Buffer management **posixio.c** (45KB) + - POSIX file I/O (open, read, write, seek) - Memory-mapped I/O option - File locking - Platform-specific optimizations **memio.c** (20KB) + - In-memory file implementation - Dynamic buffer growth - Extract to external memory **httpio.c** (8KB) + - HTTP byte-range requests - Read-only access - Caching strategy **s3io.c** (8KB) + - S3 object storage access - Byte-range requests - AWS SDK integration @@ -163,11 +181,13 @@ typedef struct NC_var { ### Core Files **hdf5dispatch.c** (152 lines) + - `HDF5_dispatcher` table - Initialization/finalization - HTTP VFD registration **nc4hdf.c** (87KB) + - Core HDF5 integration - File creation with HDF5 API - Metadata synchronization @@ -175,6 +195,7 @@ typedef struct NC_var { - Attribute handling **hdf5open.c** (99KB) + - File opening logic - HDF5 metadata reading - Dimension scale detection @@ -182,6 +203,7 @@ typedef struct NC_var { - User-defined type reconstruction **hdf5var.c** (85KB) + - Variable I/O operations - Chunking coordination with HDF5 - Filter pipeline management @@ -189,29 +211,34 @@ typedef struct NC_var { - Parallel I/O support **hdf5attr.c** (28KB) + - Attribute operations via HDF5 -- Reserved attribute handling (_NCProperties, etc.) +- Reserved attribute handling (\_NCProperties, etc.) - Type conversion for attributes **hdf5filter.c** (16KB) + - Filter plugin management - HDF5 filter pipeline integration - Compression (deflate, szip, etc.) - Custom filter registration **hdf5dim.c, hdf5grp.c, hdf5type.c** + - Dimension, group, and type operations - HDF5 dimension scales - Group hierarchy management - User-defined type translation **H5FDhttp.c** (28KB) + - HTTP Virtual File Driver - Byte-range request support - Read-only remote access - Caching strategy ### Key Features + - Uses HDF5 dimension scales for dimensions - Stores NetCDF metadata in HDF5 attributes - Supports chunking, compression, filters @@ -223,34 +250,40 @@ typedef struct NC_var { ### Core Files **zdispatch.c** (323 lines) + - `NCZ_dispatcher` table - Many operations delegate to libsrc4 - Zarr-specific implementations for I/O and metadata **zarr.c** (8KB) + - Main Zarr format implementation - Format version handling - Metadata JSON generation **zsync.c** (84KB) + - Data synchronization between memory and storage - Chunk reading/writing - Metadata persistence (.zarray, .zgroup, .zattrs) - Cache management **zvar.c** (76KB) + - Variable operations - Chunk coordinate calculation - Data assembly from chunks - Fill value handling **zfilter.c** (37KB) + - Codec pipeline implementation - Compression (blosc, zlib, etc.) - Filter chaining - Plugin support **zxcache.c** (27KB) + - Chunk cache implementation - LRU eviction - Dirty chunk tracking @@ -259,26 +292,31 @@ typedef struct NC_var { ### Storage Abstraction (zmap) **zmap.c** (11KB) + - Abstract storage interface - Key-value semantics - Backend selection **zmap_file.c** (31KB) + - Filesystem backend - Directory structure (.zarray, .zgroup files) - Atomic writes **zmap_s3sdk.c** (15KB) + - AWS S3 backend - Object storage operations - Credential management **zmap_zip.c** (22KB) + - ZIP archive backend - Read-only access - Efficient random access ### Zarr Format Details + - JSON metadata (.zarray, .zgroup, .zattrs) - Chunked storage (one file per chunk) - Codec pipeline (compression, filters) @@ -290,22 +328,26 @@ typedef struct NC_var { ### libdap2/ Files **ncd2dispatch.c** (85KB) + - `NCD2_dispatcher` table - Complete dispatch implementation - Constraint handling integration **getvara.c** (44KB) + - Maps NetCDF API to DAP requests - Constraint expression generation - Subsetting and striding - Type conversion **constraints.c** (25KB) + - DAP constraint expression parsing - Projection and selection - Optimization **cache.c** (13KB) + - HTTP response caching - Cache invalidation - Disk-based cache @@ -313,28 +355,33 @@ typedef struct NC_var { ### oc2/ - OPeNDAP Client Library **oc.c** (62KB) + - Main client implementation - Connection management - Request/response handling - Error handling **dapparse.c / daplex.c** + - DDS (Dataset Descriptor Structure) parsing - DAS (Dataset Attribute Structure) parsing - Lexical analysis **ocdata.c** (10KB) + - Binary data decoding - XDR stream processing - Data assembly **occurlfunctions.c** (9KB) + - libcurl integration - HTTP request building - Authentication - SSL/TLS support ### DAP2 Protocol + - DDS: Describes data structure - DAS: Describes attributes - DODS: Binary data response @@ -345,34 +392,41 @@ typedef struct NC_var { ### Core Files **ncd4dispatch.c** (24KB) + - `NCD4_dispatcher` table - DAP4-specific operations **d4parser.c** (49KB) + - DMR (Dataset Metadata Response) parsing - XML-based metadata - Namespace handling **d4data.c** (14KB) + - Binary data handling - Checksum verification - Data assembly **d4chunk.c** (6KB) + - Chunked response processing - Streaming data support **d4meta.c** (34KB) + - Metadata translation to NetCDF model - Group hierarchy construction - Type mapping **d4curlfunctions.c** (15KB) + - HTTP operations - Authentication - Error handling ### DAP4 Protocol + - DMR: XML metadata response - Binary data with checksums - Chunked transfer encoding @@ -381,12 +435,15 @@ typedef struct NC_var { ## Support Libraries ### libncpoco/ + Portable components for cross-platform compatibility ### libncxml/ + XML parsing for DAP4 DMR responses ### liblib/ + Additional utility code and compatibility layers ## Global State (NCglobalstate) @@ -398,21 +455,21 @@ typedef struct NCglobalstate { char* tempdir; // Temporary directory char* home; // Home directory char* cwd; // Current working directory - + struct NCRCinfo* rcinfo; // RC file configuration - + struct { size_t size; // Chunk cache size size_t nelems; // Number of elements float preemption; // Preemption policy } chunkcache; - + struct { int threshold; // Alignment threshold int alignment; // Alignment value int defined; // Whether set } alignment; - + struct { char* default_region; // AWS region char* config_file; // AWS config file @@ -420,7 +477,7 @@ typedef struct NCglobalstate { char* access_key_id; // Access key char* secret_access_key; // Secret key } aws; - + NClist* pluginpaths; // Filter plugin paths } NCglobalstate; ``` @@ -428,12 +485,14 @@ typedef struct NCglobalstate { ## Filter/Codec System ### HDF5 Filters + - Standard: deflate, shuffle, fletcher32, szip - Plugin system for custom filters - Filter IDs registered with HDF Group - Parameters passed as unsigned int arrays ### Zarr Codecs + - JSON-based codec configuration - Codec chain (multiple codecs) - Standard: blosc, zlib, gzip, lz4, zstd @@ -442,11 +501,13 @@ typedef struct NCglobalstate { ## Parallel I/O Support ### HDF5 Parallel + - Uses MPI-IO via HDF5 - Collective and independent I/O - Requires parallel HDF5 build ### PnetCDF (libsrcp/) + - Experimental parallel I/O for NetCDF-3 - MPI-IO based - Separate dispatch table diff --git a/windsurf-harnett/skills/netcdf-architecture/references/DATA-STRUCTURES.md b/windsurf-harnett/skills/netcdf-architecture/references/DATA-STRUCTURES.md index 92a3cda71..59aceef90 100644 --- a/windsurf-harnett/skills/netcdf-architecture/references/DATA-STRUCTURES.md +++ b/windsurf-harnett/skills/netcdf-architecture/references/DATA-STRUCTURES.md @@ -22,6 +22,7 @@ typedef struct NC { **Purpose**: Common handle for all open files, regardless of format. **Key Fields**: + - `ext_ncid`: The ID returned to users, managed globally - `int_ncid`: Format-specific ID (e.g., NetCDF-3 has its own ID space) - `dispatch`: Points to format-specific function table @@ -33,7 +34,7 @@ typedef struct NC { struct NC_Dispatch { int model; // NC_FORMATX_NC3, NC_FORMATX_NC4, etc. int dispatch_version; // Must match NC_DISPATCH_VERSION - + // File operations int (*create)(const char *path, int cmode, ...); int (*open)(const char *path, int mode, ...); @@ -42,16 +43,16 @@ struct NC_Dispatch { int (*sync)(int); int (*abort)(int); int (*close)(int, void *); - + // Metadata operations int (*def_dim)(int, const char *, size_t, int *); int (*def_var)(int, const char *, nc_type, int, const int *, int *); int (*put_att)(int, int, const char *, nc_type, size_t, const void *, nc_type); - + // Variable I/O int (*get_vara)(int, int, const size_t *, const size_t *, void *, nc_type); int (*put_vara)(int, int, const size_t *, const size_t *, const void *, nc_type); - + // ... ~60 more function pointers }; ``` @@ -59,6 +60,7 @@ struct NC_Dispatch { **Location**: `include/netcdf_dispatch.h:34-256` **Implementations**: + - `NC3_dispatcher` - NetCDF-3 (libsrc/nc3dispatch.c) - `HDF5_dispatcher` - HDF5 (libhdf5/hdf5dispatch.c) - `NCZ_dispatcher` - Zarr (libnczarr/zdispatch.c) @@ -77,11 +79,11 @@ typedef struct NC3_INFO { size_t begin_rec; // Offset to record variables size_t recsize; // Size of one record size_t numrecs; // Number of records written - + NC_dimarray dims; // Dimensions NC_attrarray attrs; // Global attributes NC_vararray vars; // Variables - + ncio* nciop; // I/O provider int flags; // File flags int old_format; // CDF-1, CDF-2, or CDF-5 @@ -110,7 +112,7 @@ typedef struct NC_var { nc_type type; // Data type size_t len; // Product of dimension sizes size_t begin; // Offset in file - + // For record variables size_t* shape; // Cached dimension sizes size_t* dsizes; // Cached dimension products @@ -153,7 +155,7 @@ typedef struct NC_FILE_INFO_T { int no_write; // Read-only flag int ignore_att_convention; // Ignore _NCProperties void* format_file_info; // Format-specific data (HDF5/Zarr) - + // Provenance tracking char* provenance; int provenance_size; @@ -169,13 +171,13 @@ typedef struct NC_GRP_INFO_T { NC_OBJ hdr; // Name and ID struct NC_FILE_INFO_T* nc4_info; // Parent file struct NC_GRP_INFO_T* parent; // Parent group (NULL for root) - + NCindex* children; // Child groups NCindex* dim; // Dimensions NCindex* att; // Attributes NCindex* type; // User-defined types NCindex* vars; // Variables - + void* format_grp_info; // Format-specific data } NC_GRP_INFO_T; ``` @@ -189,11 +191,11 @@ typedef struct NC_VAR_INFO_T { NC_OBJ hdr; // Name and ID char* alt_name; // Alternate name (for format differences) struct NC_GRP_INFO_T* container; // Parent group - + size_t ndims; // Number of dimensions int* dimids; // Dimension IDs NC_DIM_INFO_T** dim; // Dimension pointers - + nc_bool_t is_new_var; // Newly created nc_bool_t was_coord_var; // Was a coordinate variable nc_bool_t became_coord_var; // Became a coordinate variable @@ -201,31 +203,31 @@ typedef struct NC_VAR_INFO_T { nc_bool_t attr_dirty; // Attributes need rewriting nc_bool_t created; // Already created in file nc_bool_t written_to; // Has data been written - + struct NC_TYPE_INFO* type_info; // Type information int atts_read; // Attributes read flag nc_bool_t meta_read; // Metadata read flag nc_bool_t coords_read; // Coordinates read flag - + NCindex* att; // Attributes - + nc_bool_t no_fill; // No fill value void* fill_value; // Fill value - + size_t* chunksizes; // Chunk sizes (if chunked) int storage; // NC_CHUNKED, NC_CONTIGUOUS, NC_COMPACT int endianness; // NC_ENDIAN_NATIVE, NC_ENDIAN_LITTLE, NC_ENDIAN_BIG int parallel_access; // NC_COLLECTIVE or NC_INDEPENDENT - + struct ChunkCache { size_t size; // Cache size in bytes size_t nelems; // Number of cache slots float preemption; // Preemption policy } chunkcache; - + int quantize_mode; // Quantization mode int nsd; // Number of significant digits - + void* format_var_info; // Format-specific data void* filters; // Filter list } NC_VAR_INFO_T; @@ -276,17 +278,17 @@ typedef struct NC_TYPE_INFO_T { nc_type nc_type_class; // NC_VLEN, NC_COMPOUND, NC_OPAQUE, NC_ENUM void* format_type_info; // Format-specific data int varsized; // Variable-sized flag - + union { struct { NClist* enum_member; // Enum members nc_type base_nc_typeid; // Base type } e; - + struct { NClist* field; // Compound fields } c; - + struct { nc_type base_nc_typeid; // Base type } v; @@ -360,7 +362,7 @@ typedef struct ncio { off_t offset; // Current offset size_t extent; // File extent size_t nciop_size; // Provider-specific size - + // Function pointers int (*rel)(ncio*, off_t, int); int (*get)(ncio*, off_t, size_t, int, void**); @@ -369,7 +371,7 @@ typedef struct ncio { int (*filesize)(ncio*, off_t*); int (*pad_length)(ncio*, off_t); int (*close)(ncio*, int); - + void* pvt; // Private data } ncio; ``` @@ -387,21 +389,21 @@ typedef struct NCglobalstate { char* tempdir; // Temporary directory char* home; // Home directory char* cwd; // Current working directory - + struct NCRCinfo* rcinfo; // RC file info - + struct { size_t size; // Chunk cache size size_t nelems; // Number of elements float preemption; // Preemption policy } chunkcache; - + struct { int threshold; // Alignment threshold int alignment; // Alignment value int defined; // Set flag } alignment; - + struct { char* default_region; // AWS region char* config_file; // Config file path @@ -409,7 +411,7 @@ typedef struct NCglobalstate { char* access_key_id; // Access key char* secret_access_key; // Secret key } aws; - + NClist* pluginpaths; // Filter plugin paths } NCglobalstate; ``` diff --git a/windsurf-harnett/skills/netcdf-architecture/references/DISPATCH-TABLES.md b/windsurf-harnett/skills/netcdf-architecture/references/DISPATCH-TABLES.md index 787364968..2f77cd627 100644 --- a/windsurf-harnett/skills/netcdf-architecture/references/DISPATCH-TABLES.md +++ b/windsurf-harnett/skills/netcdf-architecture/references/DISPATCH-TABLES.md @@ -21,6 +21,7 @@ The `NC_Dispatch` structure contains function pointers for all NetCDF operations ### Implementation Summary **Fully Implemented**: + - File operations: create, open, close, sync, abort, redef, enddef - Dimensions: def_dim, inq_dim, inq_dimid, inq_unlimdim, rename_dim - Variables: def_var, inq_var, inq_varid, rename_var @@ -30,10 +31,12 @@ The `NC_Dispatch` structure contains function pointers for all NetCDF operations - Fill values: set_fill, def_var_fill **Delegated to NCDEFAULT**: + - get_vars, put_vars (strided access) - get_varm, put_varm (mapped access) **Returns NC_ENOTNC4** (not supported): + - Groups: def_grp, rename_grp, inq_grps, inq_grp_parent - User-defined types: def_compound, def_vlen, def_enum, def_opaque - Compression: def_var_deflate, def_var_fletcher32 @@ -43,6 +46,7 @@ The `NC_Dispatch` structure contains function pointers for all NetCDF operations - Quantization: def_var_quantize **Special Implementations**: + - `inq_unlimdims`: Returns single unlimited dimension (NC3 has max 1) - `inq_ncid`: Returns same ncid (no groups) - `inq_grpname`: Returns "/" (root only) @@ -54,7 +58,7 @@ The `NC_Dispatch` structure contains function pointers for all NetCDF operations static const NC_Dispatch NC3_dispatcher = { .model = NC_FORMATX_NC3, .dispatch_version = NC_DISPATCH_VERSION, - + .create = NC3_create, .open = NC3_open, .redef = NC3_redef, @@ -62,12 +66,12 @@ static const NC_Dispatch NC3_dispatcher = { .sync = NC3_sync, .abort = NC3_abort, .close = NC3_close, - + .get_vara = NC3_get_vara, .put_vara = NC3_put_vara, .get_vars = NCDEFAULT_get_vars, .put_vars = NCDEFAULT_put_vars, - + // ... more function pointers }; ``` @@ -83,6 +87,7 @@ static const NC_Dispatch NC3_dispatcher = { ### Implementation Summary **Fully Implemented**: + - All file operations - All dimension operations (with HDF5 dimension scales) - All variable operations (with chunking, compression, filters) @@ -95,11 +100,13 @@ static const NC_Dispatch NC3_dispatcher = { - Quantization (NetCDF-4.8+) **Delegated to NCDEFAULT**: + - get_varm, put_varm (mapped access) **HDF5-Specific Features**: + - Dimension scales for dimensions -- Reserved attributes (_NCProperties, _Netcdf4Coordinates, etc.) +- Reserved attributes (\_NCProperties, \_Netcdf4Coordinates, etc.) - Filter plugins - Chunk cache tuning - Parallel I/O via MPI @@ -110,27 +117,27 @@ static const NC_Dispatch NC3_dispatcher = { static const NC_Dispatch HDF5_dispatcher = { .model = NC_FORMATX_NC4, .dispatch_version = NC_DISPATCH_VERSION, - + .create = NC4_create, .open = NC4_open, - + .def_dim = HDF5_def_dim, .inq_dim = HDF5_inq_dim, .rename_dim = HDF5_rename_dim, - + .def_var = NC4_def_var, .get_vara = NC4_get_vara, .put_vara = NC4_put_vara, .get_vars = NC4_get_vars, .put_vars = NC4_put_vars, - + .def_var_deflate = NC4_def_var_deflate, .def_var_chunking = NC4_def_var_chunking, .def_var_filter = NC4_hdf5_def_var_filter, - + .def_grp = NC4_def_grp, .def_compound = NC4_def_compound, - + // ... more function pointers }; ``` @@ -146,6 +153,7 @@ static const NC_Dispatch HDF5_dispatcher = { ### Implementation Summary **Fully Implemented**: + - File operations (create, open, close, sync) - Variable I/O (get_vara, put_vara, get_vars, put_vars) - Zarr-specific metadata operations @@ -153,15 +161,18 @@ static const NC_Dispatch HDF5_dispatcher = { - Chunk caching **Delegated to NC4 (libsrc4)**: + - Most inquiry operations (inq_type, inq_dimid, inq_varid, etc.) - Group operations (inq_grps, inq_grpname, etc.) - Many metadata operations **Returns NC_NOTNC4** (not supported): + - User-defined types (compound, vlen, enum, opaque) - Some type operations **Zarr-Specific Features**: + - JSON metadata (.zarray, .zgroup, .zattrs) - Multiple storage backends (file, S3, ZIP) - Codec pipeline (blosc, zlib, etc.) @@ -173,28 +184,28 @@ static const NC_Dispatch HDF5_dispatcher = { static const NC_Dispatch NCZ_dispatcher = { .model = NC_FORMATX_NCZARR, .dispatch_version = NC_DISPATCH_VERSION, - + .create = NCZ_create, .open = NCZ_open, .close = NCZ_close, .sync = NCZ_sync, - + .get_vara = NCZ_get_vara, .put_vara = NCZ_put_vara, .get_vars = NCZ_get_vars, .put_vars = NCZ_put_vars, - + // Many operations delegate to NC4_* .inq_type = NCZ_inq_type, // Calls NC4_inq_type .inq_dimid = NCZ_inq_dimid, // Calls NC4_inq_dimid - + .def_var_filter = NCZ_def_var_filter, .def_var_chunking = NCZ_def_var_chunking, - + // User-defined types not supported .def_compound = NC_NOTNC4_def_compound, .def_vlen = NC_NOTNC4_def_vlen, - + // ... more function pointers }; ``` @@ -210,6 +221,7 @@ static const NC_Dispatch NCZ_dispatcher = { ### Implementation Summary **Fully Implemented**: + - File operations (open, close) - Variable I/O with constraint expressions - Metadata inquiry @@ -217,12 +229,14 @@ static const NC_Dispatch NCZ_dispatcher = { - Remote data access via HTTP **Not Supported** (read-only protocol): + - create, redef, enddef - def_dim, def_var, put_att - put_vara, put_vars - All NetCDF-4 features **DAP2-Specific Features**: + - Constraint expressions for subsetting - DDS/DAS parsing - HTTP caching @@ -234,18 +248,18 @@ static const NC_Dispatch NCZ_dispatcher = { static const NC_Dispatch NCD2_dispatcher = { .model = NC_FORMATX_DAP2, .dispatch_version = NC_DISPATCH_VERSION, - + .create = NULL, // Not supported .open = NCD2_open, .close = NCD2_close, - + .get_vara = NCD2_get_vara, .put_vara = NULL, // Read-only - + .inq = NCD2_inq, .inq_var = NCD2_inq_var, .get_att = NCD2_get_att, - + // ... more function pointers }; ``` @@ -261,6 +275,7 @@ static const NC_Dispatch NCD2_dispatcher = { ### Implementation Summary **Fully Implemented**: + - File operations (open, close) - Variable I/O - Metadata inquiry (DMR parsing) @@ -268,12 +283,14 @@ static const NC_Dispatch NCD2_dispatcher = { - Enhanced type system **Not Supported** (read-only protocol): + - create, redef, enddef - def_dim, def_var, put_att - put_vara, put_vars - User-defined types (read-only) **DAP4-Specific Features**: + - DMR (XML metadata) - Groups and hierarchies - Checksums @@ -285,16 +302,16 @@ static const NC_Dispatch NCD2_dispatcher = { static const NC_Dispatch NCD4_dispatcher = { .model = NC_FORMATX_DAP4, .dispatch_version = NC_DISPATCH_VERSION, - + .create = NULL, // Not supported .open = NCD4_open, .close = NCD4_close, - + .get_vara = NCD4_get_vara, .put_vara = NULL, // Read-only - + .inq_grps = NCD4_inq_grps, // Groups supported - + // ... more function pointers }; ``` @@ -325,17 +342,19 @@ NetCDF-C provides 10 user-defined format slots that allow developers to extend t Users can register custom formats via `nc_def_user_format()`: ```c -int nc_def_user_format(int mode_flag, +int nc_def_user_format(int mode_flag, NC_Dispatch* dispatch_table, char* magic_number); ``` **Parameters**: + - `mode_flag`: One of `NC_UDF0` through `NC_UDF9`, optionally combined with other mode flags (e.g., `NC_NETCDF4`) - `dispatch_table`: Pointer to your `NC_Dispatch` structure - `magic_number`: Optional magic number string (max `NC_MAX_MAGIC_NUMBER_LEN` bytes) for automatic format detection, or NULL **Example**: + ```c extern NC_Dispatch my_format_dispatcher; @@ -352,12 +371,13 @@ nc_open("myfile.dat", 0, &ncid); // Auto-detects format Use `nc_inq_user_format()` to query registered formats: ```c -int nc_inq_user_format(int mode_flag, +int nc_inq_user_format(int mode_flag, NC_Dispatch** dispatch_table, char* magic_number); ``` **Example**: + ```c NC_Dispatch *disp; char magic[NC_MAX_MAGIC_NUMBER_LEN + 1]; @@ -369,6 +389,7 @@ nc_inq_user_format(NC_UDF0, &disp, magic); UDFs can be automatically loaded from RC file configuration: **RC File Format** (`.ncrc`, `.daprc`, or `.dodsrc`): + ```ini NETCDF.UDF.LIBRARY=/full/path/to/library.so NETCDF.UDF.INIT=initialization_function_name @@ -376,6 +397,7 @@ NETCDF.UDF.MAGIC=OPTIONAL_MAGIC_NUMBER ``` **Example**: + ```ini # Load custom format in UDF0 NETCDF.UDF0.LIBRARY=/usr/local/lib/libmyformat.so @@ -389,12 +411,14 @@ NETCDF.UDF3.MAGIC=SCIDATA ``` **RC File Requirements**: + - `LIBRARY`: Must be a full absolute path to the shared library - `INIT`: Name of the initialization function in the library - `MAGIC`: Optional magic number for automatic format detection - Both `LIBRARY` and `INIT` must be present; partial configuration is ignored with a warning **RC File Search Order**: + 1. `$HOME/.ncrc` 2. `$HOME/.daprc` 3. `$HOME/.dodsrc` @@ -420,17 +444,20 @@ Plugins are loaded during library initialization (`nc_initialize()`): ### Plugin Implementation Requirements **Dispatch Table Requirements**: + - Dispatch table version must match `NC_DISPATCH_VERSION` - Must implement all required operations or use pre-defined stubs - Magic number max `NC_MAX_MAGIC_NUMBER_LEN` bytes (optional) **Initialization Function Requirements**: + 1. Must be exported (not static) 2. Must call `nc_def_user_format()` to register dispatch table 3. Should return `NC_NOERR` on success, error code on failure 4. Name must match RC file `INIT` key **Example Initialization Function**: + ```c #include @@ -439,17 +466,17 @@ extern NC_Dispatch my_dispatcher; // Initialization function - must be exported int my_plugin_init(void) { int ret; - + // Register dispatch table with magic number - ret = nc_def_user_format(NC_UDF0 | NC_NETCDF4, + ret = nc_def_user_format(NC_UDF0 | NC_NETCDF4, &my_dispatcher, "MYFMT"); if (ret != NC_NOERR) return ret; - + // Additional initialization if needed // ... - + return NC_NOERR; } ``` @@ -459,27 +486,33 @@ int my_plugin_init(void) { For operations your format doesn't support, use these pre-defined functions: **Read-only stubs** (`libdispatch/dreadonly.c`): + - `NC_RO_create`, `NC_RO_redef`, `NC_RO__enddef`, `NC_RO_sync` - `NC_RO_set_fill`, `NC_RO_def_dim`, `NC_RO_def_var`, `NC_RO_put_att` - `NC_RO_put_vara`, `NC_RO_put_vars`, `NC_RO_put_varm` - Returns `NC_EPERM` (operation not permitted) **Not NetCDF-4 stubs** (`libdispatch/dnotnc4.c`): + - `NC_NOTNC4_def_grp`, `NC_NOTNC4_def_compound`, `NC_NOTNC4_def_vlen` - `NC_NOTNC4_def_var_deflate`, `NC_NOTNC4_def_var_chunking` - Returns `NC_ENOTNC4` (not a NetCDF-4 file) **Not NetCDF-3 stubs** (`libdispatch/dnotnc3.c`): + - Returns `NC_ENOTNC3` (not a NetCDF-3 file) **No-op stubs**: + - `NC_NOOP_*` - Returns `NC_NOERR` without doing anything **Default implementations** (`libdispatch/dvar.c`): + - `NCDEFAULT_get_vars`, `NCDEFAULT_put_vars` - Strided access using get_vara/put_vara - `NCDEFAULT_get_varm`, `NCDEFAULT_put_varm` - Mapped access using get_vars/put_vars **NetCDF-4 inquiry functions** (`libsrc4/`): + - `NC4_inq`, `NC4_inq_type`, `NC4_inq_dimid`, `NC4_inq_varid` - Use internal metadata model for inquiry operations @@ -491,7 +524,7 @@ For operations your format doesn't support, use these pre-defined functions: static NC_Dispatch my_dispatcher = { NC_FORMATX_UDF0, /* Use UDF slot 0 */ NC_DISPATCH_VERSION, /* Current ABI version */ - + NC_RO_create, /* Read-only: use predefined function */ my_open, /* Custom open function */ NC_RO_redef, @@ -502,13 +535,13 @@ static NC_Dispatch my_dispatcher = { NC_RO_set_fill, my_inq_format, my_inq_format_extended, - + /* Inquiry functions - can use NC4_* defaults */ NC4_inq, NC4_inq_type, NC4_inq_dimid, NC4_inq_varid, - + /* Variable I/O */ my_get_vara, NC_RO_put_vara, /* Read-only */ @@ -516,14 +549,14 @@ static NC_Dispatch my_dispatcher = { NC_RO_put_vars, NCDEFAULT_get_varm, /* Use default mapped implementation */ NC_RO_put_varm, - + /* NetCDF-4 features not supported */ NC_NOTNC4_def_grp, NC_NOTNC4_def_compound, NC_NOTNC4_def_vlen, NC_NOTNC4_def_var_deflate, NC_NOTNC4_def_var_chunking, - + /* ... continue for all ~70 function pointers ... */ }; ``` @@ -533,12 +566,14 @@ static NC_Dispatch my_dispatcher = { Magic numbers enable automatic format detection when opening files. **How Magic Numbers Work**: + 1. When `nc_open()` is called without a specific format flag 2. The file's first bytes are read 3. They are compared against all registered magic numbers (built-in and user-defined) 4. If a match is found, the corresponding dispatcher is used **Magic Number Best Practices**: + - Use unique, distinctive strings (4-8 bytes recommended) - Place at the beginning of your file format - Avoid conflicts with existing formats: @@ -549,11 +584,13 @@ Magic numbers enable automatic format detection when opening files. ### Platform Considerations **Unix/Linux/macOS**: + - Shared libraries: `.so` extension - Dynamic loading: `dlopen()` and `dlsym()` - Library paths: Use absolute paths or ensure libraries are in `LD_LIBRARY_PATH` **Windows**: + - Shared libraries: `.dll` extension - Dynamic loading: `LoadLibrary()` and `GetProcAddress()` - Library paths: Use absolute paths or ensure DLLs are in system `PATH` @@ -561,11 +598,13 @@ Magic numbers enable automatic format detection when opening files. **Building Plugins**: Unix: + ```bash gcc -shared -fPIC -o libmyplugin.so myplugin.c -lnetcdf ``` Windows: + ```batch cl /LD myplugin.c netcdf.lib ``` @@ -580,30 +619,36 @@ cl /LD myplugin.c netcdf.lib ### Common Errors **NC_EINVAL: Invalid dispatch table version** + - Cause: Plugin was compiled against a different version of NetCDF-C - Solution: Recompile plugin against current NetCDF-C version **Plugin not loaded (no error)** + - Cause: Partial RC configuration (LIBRARY without INIT, or vice versa) - Solution: Check that both LIBRARY and INIT keys are present in RC file **Library not found** -- Cause: Incorrect path in NETCDF.UDF*.LIBRARY + +- Cause: Incorrect path in NETCDF.UDF\*.LIBRARY - Solution: Use absolute path; verify file exists and has correct permissions **Init function not found** + - Cause: Function name mismatch or missing export - Solution: Verify function name matches INIT key; ensure function is exported (not static) ### Testing UDFs **Enable Logging**: + ```bash export NC_LOG_LEVEL=3 ./myprogram ``` **Verify RC File is Read**: + ```bash echo "NETCDF.UDF0.LIBRARY=/tmp/test.so" > ~/.ncrc echo "NETCDF.UDF0.INIT=test_init" >> ~/.ncrc @@ -611,11 +656,13 @@ echo "NETCDF.UDF0.INIT=test_init" >> ~/.ncrc ``` **Check Plugin Exports** (Unix): + ```bash nm -D libmyplugin.so | grep init ``` **Check Plugin Exports** (Windows): + ```batch dumpbin /EXPORTS myplugin.dll ``` @@ -654,6 +701,7 @@ dumpbin /EXPORTS myplugin.dll ### Dispatch Table Registration **Initialization** (called at library startup): + ```c NCDISPATCH_initialize() → NC3_initialize() // Sets NC3_dispatch_table @@ -666,6 +714,7 @@ NCDISPATCH_initialize() ## Function Pointer Conventions ### Return Values + - `NC_NOERR` (0) on success - Negative error codes on failure - `NC_ENOTNC4` for unsupported NetCDF-4 features @@ -673,9 +722,9 @@ NCDISPATCH_initialize() ### Common Stubs -**NC_NOOP_*** - No-operation stubs (return NC_NOERR) -**NC_NOTNC4_*** - Not-NetCDF-4 stubs (return NC_ENOTNC4) -**NCDEFAULT_*** - Default implementations (in libdispatch) +**NC*NOOP*\*** - No-operation stubs (return NC\*NOERR) +\*\*NC_NOTNC4**\*\* - Not-NetCDF-4 stubs (return NC_ENOTNC4) +**NCDEFAULT\_\*\*\* - Default implementations (in libdispatch) ### NCDEFAULT Implementations @@ -699,6 +748,7 @@ NCDISPATCH_initialize() ## Testing Dispatch Tables Each format has its own test suite: + - `nc_test/` - NetCDF-3 tests - `nc_test4/` - NetCDF-4/HDF5 tests - `nczarr_test/` - Zarr tests diff --git a/windsurf-harnett/skills/netcdf-architecture/references/EXAMPLES.md b/windsurf-harnett/skills/netcdf-architecture/references/EXAMPLES.md index 379377c05..0c3aa5fe8 100644 --- a/windsurf-harnett/skills/netcdf-architecture/references/EXAMPLES.md +++ b/windsurf-harnett/skills/netcdf-architecture/references/EXAMPLES.md @@ -5,6 +5,7 @@ This reference provides practical examples and common programming patterns for w ## Overview NetCDF-C provides example programs demonstrating: + - Basic file creation and reading (NetCDF-3) - Enhanced features (NetCDF-4: groups, compression, user-defined types) - Real-world patterns (meteorological data, time series, multidimensional arrays) @@ -24,6 +25,7 @@ NetCDF-C provides example programs demonstrating: **What it creates**: 2D array (6x12) with dimensions "x" and "y", variable "data" **Key Pattern - File Creation**: + ```c int ncid, x_dimid, y_dimid, varid; int dimids[2]; @@ -51,6 +53,7 @@ nc_close(ncid); ``` **Key Pattern - File Reading**: + ```c int ncid, varid; int data_in[NX][NY]; @@ -77,6 +80,7 @@ nc_close(ncid); **What it creates**: Surface temperature and pressure on 6x12 lat/lon grid with metadata **Key Pattern - Adding Attributes**: + ```c // Define variable nc_def_var(ncid, "temperature", NC_FLOAT, 2, dimids, &temp_varid); @@ -89,6 +93,7 @@ nc_put_att_text(ncid, NC_GLOBAL, "title", 23, "Surface Temperature Data"); ``` **Key Pattern - Coordinate Variables**: + ```c // Define latitude dimension nc_def_dim(ncid, "latitude", NLAT, &lat_dimid); @@ -113,6 +118,7 @@ nc_put_var_float(ncid, lat_varid, lats); **What it creates**: Temperature and pressure with dimensions [time, level, lat, lon] **Key Pattern - Unlimited Dimension**: + ```c // Define unlimited dimension (use NC_UNLIMITED for size) nc_def_dim(ncid, "time", NC_UNLIMITED, &time_dimid); @@ -123,22 +129,24 @@ nc_def_var(ncid, "temperature", NC_FLOAT, 4, dimids, &temp_varid); ``` **Key Pattern - Writing Time Steps**: + ```c // Write one time step at a time for (int rec = 0; rec < NREC; rec++) { size_t start[4] = {rec, 0, 0, 0}; // Start at this time step size_t count[4] = {1, NLVL, NLAT, NLON}; // Write one time slice - + // Prepare data for this time step float temp_out[NLVL][NLAT][NLON]; // ... fill temp_out ... - + // Write hyperslab nc_put_vara_float(ncid, temp_varid, start, count, &temp_out[0][0][0]); } ``` **Key Pattern - Reading Time Steps**: + ```c // Read one time step size_t start[4] = {rec, 0, 0, 0}; @@ -157,6 +165,7 @@ nc_get_vara_float(ncid, temp_varid, start, count, &temp_in[0][0][0]); **What it creates**: Two groups with different data types (uint64 and compound) **Key Pattern - Creating Groups**: + ```c int ncid, grp1_id, grp2_id; @@ -172,6 +181,7 @@ nc_def_var(grp1_id, "data", NC_UINT64, 2, dimids, &varid); ``` **Key Pattern - Compound Types**: + ```c typedef struct { int i1; @@ -182,7 +192,7 @@ nc_type compound_typeid; // Define compound type nc_def_compound(grp2_id, sizeof(compound_data), "compound_t", &compound_typeid); -nc_insert_compound(grp2_id, compound_typeid, "i1", +nc_insert_compound(grp2_id, compound_typeid, "i1", NC_COMPOUND_OFFSET(compound_data, i1), NC_INT); nc_insert_compound(grp2_id, compound_typeid, "i2", NC_COMPOUND_OFFSET(compound_data, i2), NC_INT); @@ -198,6 +208,7 @@ nc_def_var(grp2_id, "data", compound_typeid, 2, dimids, &varid); **Purpose**: Demonstrates chunking, compression, and checksums (HDF5 features). **Key Pattern - Chunking**: + ```c // Define variable nc_def_var(ncid, "data", NC_INT, 2, dimids, &varid); @@ -208,6 +219,7 @@ nc_def_var_chunking(ncid, varid, NC_CHUNKED, chunks); ``` **Key Pattern - Compression**: + ```c // Enable deflate compression (level 1-9, 9 = maximum compression) int shuffle = NC_SHUFFLE; // Shuffle filter improves compression @@ -217,6 +229,7 @@ nc_def_var_deflate(ncid, varid, shuffle, deflate, deflate_level); ``` **Key Pattern - Checksums**: + ```c // Enable fletcher32 checksum for data integrity nc_def_var_fletcher32(ncid, varid, NC_FLETCHER32); @@ -229,6 +242,7 @@ nc_def_var_fletcher32(ncid, varid, NC_FLETCHER32); **Purpose**: Demonstrates using custom compression filters (e.g., bzip2). **Key Pattern - Custom Filter**: + ```c // Define variable with chunking (required for filters) nc_def_var(ncid, "data", NC_INT, 2, dimids, &varid); @@ -247,6 +261,7 @@ nc_def_var_filter(ncid, varid, filter_id, nparams, params); ### Pattern 1: Error Handling **Always check return codes**: + ```c int retval; @@ -260,6 +275,7 @@ if ((retval = nc_create(FILE_NAME, NC_CLOBBER, &ncid))) ### Pattern 2: Inquiry Functions **Get file information without prior knowledge**: + ```c int ncid, ndims, nvars, ngatts, unlimdimid; @@ -278,13 +294,14 @@ nc_inq_dim(ncid, dimid, dim_name, &dim_len); char var_name[NC_MAX_NAME+1]; nc_type var_type; int var_ndims, var_dimids[NC_MAX_VAR_DIMS], var_natts; -nc_inq_var(ncid, varid, var_name, &var_type, &var_ndims, +nc_inq_var(ncid, varid, var_name, &var_type, &var_ndims, var_dimids, &var_natts); ``` ### Pattern 3: Subsetting Data (Hyperslabs) **Read/write portions of arrays**: + ```c // Read a subset: time=5, all levels, lat 10-20, lon 30-40 size_t start[4] = {5, 0, 10, 30}; @@ -297,6 +314,7 @@ nc_get_vara_float(ncid, varid, start, count, &subset[0][0][0]); ### Pattern 4: Strided Access **Read every Nth element**: + ```c // Read every 2nd element in each dimension size_t start[2] = {0, 0}; @@ -309,6 +327,7 @@ nc_get_vars_float(ncid, varid, start, count, stride, data); ### Pattern 5: Fill Values **Handle missing data**: + ```c // Set custom fill value float fill_value = -999.0; @@ -321,6 +340,7 @@ nc_def_var_fill(ncid, varid, NC_NOFILL, NULL); ### Pattern 6: Parallel I/O (NetCDF-4 with HDF5) **MPI parallel access**: + ```c #include @@ -330,7 +350,7 @@ MPI_Comm_size(MPI_COMM_WORLD, &nprocs); MPI_Comm_rank(MPI_COMM_WORLD, &rank); // Create file with parallel access -nc_create_par("parallel.nc", NC_NETCDF4|NC_MPIIO, +nc_create_par("parallel.nc", NC_NETCDF4|NC_MPIIO, MPI_COMM_WORLD, MPI_INFO_NULL, &ncid); // Set collective access for variable @@ -349,6 +369,7 @@ MPI_Finalize(); ## Best Practices from Examples ### File Creation + 1. **Always use NC_CLOBBER or NC_NOCLOBBER** to control overwrite behavior 2. **Classic CDF-1 is the default**: `nc_create(path, NC_CLOBBER, &ncid)` creates a classic file — no format flag needed 3. **NC_CLASSIC_MODEL is only for NetCDF-4**: Use `NC_NETCDF4 | NC_CLASSIC_MODEL` to get HDF5 storage with classic data model restrictions (no groups, no user-defined types). Do NOT use `NC_CLASSIC_MODEL` alone for classic CDF-1 files. @@ -356,27 +377,32 @@ MPI_Finalize(); 5. **Close files** with `nc_close()` to ensure data is flushed ### Dimensions + 1. **Unlimited dimension first** in dimension order for best performance 2. **One unlimited dimension** in NetCDF-3, multiple allowed in NetCDF-4 3. **Coordinate variables** should match dimension names ### Variables + 1. **Add units attribute** to all data variables (CF convention) 2. **Use appropriate data types** (NC_FLOAT for most scientific data) 3. **Enable chunking** before compression or filters ### Attributes + 1. **Use standard names** from CF conventions when possible 2. **Add global attributes** for file-level metadata (title, history, etc.) -3. **Document missing values** with _FillValue or missing_value attributes +3. **Document missing values** with \_FillValue or missing_value attributes ### Performance + 1. **Use chunking** for large datasets accessed in subsets 2. **Enable compression** to reduce file size (deflate level 5 is good default) 3. **Write contiguously** when possible (avoid random access) 4. **Use collective I/O** in parallel applications ### NetCDF-4 Features + 1. **Use groups** to organize related variables 2. **Compound types** for structured data (like C structs) 3. **Compression** is transparent to readers @@ -387,18 +413,21 @@ MPI_Finalize(); The NetCDF-C tutorial covers these key areas: ### Data Model + - **Classic Model**: Dimensions, variables, attributes - **Enhanced Model**: Groups, user-defined types, multiple unlimited dimensions - **Unlimited Dimensions**: Growing datasets (time series) - **Strings**: NC_STRING type in NetCDF-4 ### File Operations + - **Creating Files**: Define mode vs data mode - **Reading Known Structure**: When you know the schema - **Reading Unknown Structure**: Generic file inspection - **Subsets**: Hyperslabs, strides, mapped access ### Advanced Topics + - **Error Handling**: Return codes and nc_strerror() - **HDF5 Interoperability**: Reading HDF5 files as NetCDF - **Parallel I/O**: MPI-based parallel access @@ -407,6 +436,7 @@ The NetCDF-C tutorial covers these key areas: ## Command-Line Tools ### ncdump - Examine Files + ```bash # View file structure ncdump -h file.nc @@ -419,6 +449,7 @@ ncdump -c file.nc ``` ### ncgen - Generate Files from CDL + ```bash # Create NetCDF file from CDL ncgen -o output.nc input.cdl @@ -428,6 +459,7 @@ ncgen -k nc4 -o output.nc input.cdl ``` ### nccopy - Copy and Convert + ```bash # Convert NetCDF-3 to NetCDF-4 with compression nccopy -k nc4 -d 5 input.nc output.nc @@ -439,6 +471,7 @@ nccopy -c "var:10,20,30" input.nc output.nc ## Example File Locations In the NetCDF-C source tree: + - `examples/C/` - C examples - `examples/CDL/` - CDL files for ncgen - `nc_test/` - Test programs (also good examples) @@ -447,14 +480,17 @@ In the NetCDF-C source tree: ## Additional Resources **Official Documentation**: + - Tutorial: https://docs.unidata.ucar.edu/netcdf-c/current/tutorial_8dox.html - Examples: https://docs.unidata.ucar.edu/netcdf-c/current/examples1.html - API Reference: https://docs.unidata.ucar.edu/netcdf-c/current/modules.html **CF Conventions**: + - http://cfconventions.org/ - Climate and Forecast metadata conventions **Best Practices**: + - Use coordinate variables for dimensions - Include units attributes - Add descriptive global attributes diff --git a/windsurf-harnett/skills/netcdf-architecture/references/FORTRAN-INTERFACE.md b/windsurf-harnett/skills/netcdf-architecture/references/FORTRAN-INTERFACE.md index 099ee436f..67d32bba9 100644 --- a/windsurf-harnett/skills/netcdf-architecture/references/FORTRAN-INTERFACE.md +++ b/windsurf-harnett/skills/netcdf-architecture/references/FORTRAN-INTERFACE.md @@ -174,25 +174,26 @@ status = nf90_close(ncid) ### NetCDF External Types and Fortran Constants | NetCDF Type | Fortran 90 Constant | Bits | -|-------------|---------------------|------| -| byte | NF90_BYTE | 8 | -| char | NF90_CHAR | 8 | -| short | NF90_SHORT | 16 | -| int | NF90_INT | 32 | -| float | NF90_FLOAT | 32 | -| double | NF90_DOUBLE | 64 | -| ubyte | NF90_UBYTE | 8 | -| ushort | NF90_USHORT | 16 | -| uint | NF90_UINT | 32 | -| int64 | NF90_INT64 | 64 | -| uint64 | NF90_UINT64 | 64 | -| string | NF90_STRING | - | +| ----------- | ------------------- | ---- | +| byte | NF90_BYTE | 8 | +| char | NF90_CHAR | 8 | +| short | NF90_SHORT | 16 | +| int | NF90_INT | 32 | +| float | NF90_FLOAT | 32 | +| double | NF90_DOUBLE | 64 | +| ubyte | NF90_UBYTE | 8 | +| ushort | NF90_USHORT | 16 | +| uint | NF90_UINT | 32 | +| int64 | NF90_INT64 | 64 | +| uint64 | NF90_UINT64 | 64 | +| string | NF90_STRING | - | ## Variable I/O Flexibility The `NF90_PUT_VAR` and `NF90_GET_VAR` functions support flexible data access: ### Basic Usage + ```fortran ! Write entire array status = nf90_put_var(ncid, varid, data_array) @@ -202,6 +203,7 @@ status = nf90_get_var(ncid, varid, data_array) ``` ### Subsetting with start/count + ```fortran ! Write a subset starting at index (10,20) with size (5,10) status = nf90_put_var(ncid, varid, data_array, & @@ -209,6 +211,7 @@ status = nf90_put_var(ncid, varid, data_array, & ``` ### Strided Access + ```fortran ! Read every other element status = nf90_get_var(ncid, varid, data_array, & @@ -216,6 +219,7 @@ status = nf90_get_var(ncid, varid, data_array, & ``` ### Mapped Access + ```fortran ! Non-contiguous memory mapping status = nf90_put_var(ncid, varid, data_array, & @@ -311,31 +315,37 @@ call MPI_Finalize(ierr) ## Best Practices ### 1. Always Check Return Status + ```fortran if (status /= NF90_NOERR) call handle_error(status) ``` ### 2. Close Files Explicitly + ```fortran status = nf90_close(ncid) ``` ### 3. Use NF90_SYNC for Critical Data + ```fortran ! Ensure data is written to disk status = nf90_sync(ncid) ``` ### 4. Minimize Define Mode Transitions + Define all dimensions, variables, and attributes before entering data mode to avoid performance overhead. ### 5. Use Chunking for Large Arrays (NetCDF-4) + ```fortran status = nf90_def_var(ncid, "data", NF90_FLOAT, dimids, varid, & chunksizes=[100,100,1]) ``` ### 6. Enable Compression for Large Datasets + ```fortran status = nf90_def_var(ncid, "data", NF90_FLOAT, dimids, varid, & deflate_level=4, shuffle=.true.) diff --git a/windsurf-harnett/skills/netcdf-architecture/references/UDF-PLUGINS.md b/windsurf-harnett/skills/netcdf-architecture/references/UDF-PLUGINS.md index 89bbf2fca..de353e882 100644 --- a/windsurf-harnett/skills/netcdf-architecture/references/UDF-PLUGINS.md +++ b/windsurf-harnett/skills/netcdf-architecture/references/UDF-PLUGINS.md @@ -32,7 +32,7 @@ A UDF plugin consists of three main components: typedef struct NC_Dispatch { int model; /* NC_FORMATX_UDF0 through NC_FORMATX_UDF9 */ int dispatch_version; /* Must be NC_DISPATCH_VERSION */ - + /* Function pointers for all netCDF operations (~70 total) */ int (*create)(...); int (*open)(...); @@ -53,7 +53,7 @@ typedef struct NC_Dispatch { static NC_Dispatch my_dispatcher = { NC_FORMATX_UDF0, /* Use UDF slot 0 */ NC_DISPATCH_VERSION, /* Current ABI version */ - + NC_RO_create, /* Read-only: use predefined function */ my_open, /* Custom open function */ NC_RO_redef, @@ -64,7 +64,7 @@ static NC_Dispatch my_dispatcher = { NC_RO_set_fill, my_inq_format, my_inq_format_extended, - + /* Inquiry functions - can use NC4_* defaults */ NC4_inq, NC4_inq_type, @@ -81,7 +81,7 @@ static NC_Dispatch my_dispatcher = { NC4_inq_type_equal, NC4_inq_user_type, NC4_inq_typeid, - + /* Variable I/O */ my_get_vara, NC_RO_put_vara, /* Read-only */ @@ -89,24 +89,24 @@ static NC_Dispatch my_dispatcher = { NC_RO_put_vars, NCDEFAULT_get_varm, /* Use default mapped implementation */ NC_RO_put_varm, - + /* Attributes */ NC4_get_att, NC_RO_put_att, - + /* Dimensions */ NC4_inq_dim, NC_RO_def_dim, NC4_inq_unlimdims, NC_RO_rename_dim, - + /* Variables */ NC4_inq_var_all, NC_RO_def_var, NC_RO_rename_var, NC4_var_par_access, NC_RO_def_var_fill, - + /* NetCDF-4 features not supported */ NC_NOTNC4_show_metadata, NC_NOTNC4_inq_grps, @@ -147,6 +147,7 @@ Use these for operations your format doesn't support: **File**: `libdispatch/dreadonly.c` Returns `NC_EPERM` (operation not permitted): + - `NC_RO_create` - File creation - `NC_RO_redef` - Enter define mode - `NC_RO__enddef` - Leave define mode @@ -168,6 +169,7 @@ Returns `NC_EPERM` (operation not permitted): **File**: `libdispatch/dnotnc4.c` Returns `NC_ENOTNC4` (not a NetCDF-4 file): + - `NC_NOTNC4_def_grp` - Define group - `NC_NOTNC4_rename_grp` - Rename group - `NC_NOTNC4_def_compound` - Define compound type @@ -186,6 +188,7 @@ Returns `NC_ENOTNC4` (not a NetCDF-4 file): **File**: `libdispatch/dvar.c` Generic implementations built on simpler operations: + - `NCDEFAULT_get_vars` - Strided read using `get_vara` - `NCDEFAULT_put_vars` - Strided write using `put_vara` - `NCDEFAULT_get_varm` - Mapped read using `get_vars` @@ -196,6 +199,7 @@ Generic implementations built on simpler operations: **Files**: `libsrc4/*.c` Use internal metadata model for inquiry operations: + - `NC4_inq` - Inquire about file - `NC4_inq_type` - Inquire about type - `NC4_inq_dimid` - Get dimension ID @@ -234,17 +238,17 @@ extern NC_Dispatch my_dispatcher; int my_plugin_init(void) { int ret; - + /* Register dispatch table with magic number */ - ret = nc_def_user_format(NC_UDF0 | NC_NETCDF4, + ret = nc_def_user_format(NC_UDF0 | NC_NETCDF4, &my_dispatcher, "MYFMT"); if (ret != NC_NOERR) return ret; - + /* Additional initialization if needed */ /* ... */ - + return NC_NOERR; } ``` @@ -261,7 +265,7 @@ int my_open(const char *path, int mode, int basepe, size_t *chunksizehintp, /* 2. Populate internal metadata structures */ /* 3. Store format-specific data in NC->dispatchdata */ /* 4. Return NC_NOERR on success */ - + return NC_NOERR; } ``` @@ -274,7 +278,7 @@ int my_close(int ncid, void *v) /* 1. Clean up resources */ /* 2. Close file handles */ /* 3. Free format-specific data */ - + return NC_NOERR; } ``` @@ -287,7 +291,7 @@ int my_abort(int ncid, void *v) /* 1. Discard any pending changes */ /* 2. Clean up resources */ /* 3. Close file handles */ - + return NC_NOERR; } ``` @@ -322,7 +326,7 @@ int my_get_vara(int ncid, int varid, const size_t *start, /* 2. Read data from your format */ /* 3. Convert to requested memory type if needed */ /* 4. Copy to value buffer */ - + return NC_NOERR; } ``` @@ -332,6 +336,7 @@ int my_get_vara(int ncid, int varid, const size_t *start, ### Unix/Linux/macOS **Makefile**: + ```makefile CC = gcc CFLAGS = -fPIC -I/usr/local/include @@ -345,6 +350,7 @@ install: ``` **Command line**: + ```bash gcc -shared -fPIC -I/usr/local/include -o myplugin.so myplugin.c -lnetcdf ``` @@ -352,6 +358,7 @@ gcc -shared -fPIC -I/usr/local/include -o myplugin.so myplugin.c -lnetcdf ### Windows **Command line**: + ```batch cl /LD /I"C:\netcdf\include" myplugin.c /link /LIBPATH:"C:\netcdf\lib" netcdf.lib ``` @@ -386,16 +393,16 @@ extern int my_plugin_init(void); int main() { int ret; NC_Dispatch *disp; - + /* Test initialization */ ret = my_plugin_init(); assert(ret == NC_NOERR); - + /* Verify registration */ ret = nc_inq_user_format(NC_UDF0, &disp, NULL); assert(ret == NC_NOERR); assert(disp == &my_dispatcher); - + printf("Plugin tests passed\n"); return 0; } @@ -409,21 +416,21 @@ int main() { int main() { int ncid, ret; - + /* Initialize and register plugin */ my_plugin_init(); - + /* Test file operations */ ret = nc_open("testfile.dat", NC_UDF0, &ncid); if (ret != NC_NOERR) { fprintf(stderr, "Open failed: %s\n", nc_strerror(ret)); return 1; } - + /* Test operations */ int format; nc_inq_format(ncid, &format); - + nc_close(ncid); printf("Integration test passed\n"); return 0; @@ -433,6 +440,7 @@ int main() { ### RC File Testing Create `.ncrc`: + ```ini NETCDF.UDF0.LIBRARY=/path/to/myplugin.so NETCDF.UDF0.INIT=my_plugin_init @@ -440,6 +448,7 @@ NETCDF.UDF0.MAGIC=MYFMT ``` Test automatic loading: + ```c int main() { /* Plugin loads automatically during nc_initialize() */ @@ -462,11 +471,13 @@ export NC_LOG_LEVEL=3 ### Check Symbol Exports **Unix**: + ```bash nm -D libmyplugin.so | grep init ``` **Windows**: + ```batch dumpbin /EXPORTS myplugin.dll ``` @@ -483,16 +494,19 @@ gdb ./test_program ### Common Issues **Plugin not loaded**: + - Check RC file syntax - Verify both LIBRARY and INIT are present - Use absolute path for LIBRARY **Init function not found**: + - Ensure function is not static - Check function name matches INIT key - Verify symbol is exported **ABI version mismatch**: + - Recompile against current netCDF-C headers - Check `NC_DISPATCH_VERSION` value @@ -511,6 +525,7 @@ gdb ./test_program ### How They Work When `nc_open()` is called without a specific format flag: + 1. File's first bytes are read 2. Compared against all registered magic numbers 3. If match found, corresponding UDF dispatcher is used diff --git a/windsurf-harnett/skills/netcdf-java/skill.md b/windsurf-harnett/skills/netcdf-java/skill.md index 42286c453..9d678a928 100644 --- a/windsurf-harnett/skills/netcdf-java/skill.md +++ b/windsurf-harnett/skills/netcdf-java/skill.md @@ -1,9 +1,11 @@ # NetCDF-Java Library ## Overview + The netCDF-Java library is a 100% Java framework for reading and writing scientific data formats. It implements the Common Data Model (CDM), which is an abstract data model that merges netCDF, OPeNDAP, and HDF5 data models to create a unified API for accessing many types of scientific data. **Key Capabilities:** + - Read netCDF-3, netCDF-4, HDF5, GRIB, BUFR, and many other scientific data formats - Write netCDF-3 files natively - Write netCDF-4 files via JNI to netCDF-C library @@ -13,6 +15,7 @@ The netCDF-Java library is a 100% Java framework for reading and writing scienti - Scientific feature type support (grids, point data, radial data, etc.) ## Documentation and Resources + - **GitHub Repository:** https://github.com/Unidata/netcdf-java - **Main Documentation:** https://docs.unidata.ucar.edu/netcdf-java/current/userguide/ - **API Reference:** Available through Maven artifacts @@ -24,20 +27,26 @@ The netCDF-Java library is a 100% Java framework for reading and writing scienti The CDM has three layers that build on each other: ### 1. Data Access Layer (Syntactic Layer) + Handles data reading and writing through: + - **NetcdfFile:** Read-only access to datasets - **NetcdfFiles:** Static methods for opening files - **IOServiceProvider:** Interface for format-specific implementations - **Variable, Dimension, Attribute, Group, Structure:** Metadata objects ### 2. Coordinate System Layer + Identifies coordinates of data arrays: + - General coordinate concepts for scientific data - Specialized georeferencing coordinate systems for Earth Science - CoordinateAxis and CoordinateSystem objects ### 3. Scientific Feature Types Layer + Specialized methods for specific data types: + - Grids - Point data - Radial data (radar, lidar) @@ -52,21 +61,21 @@ Specialized methods for specific data types: // Open a NetCDF file try (NetcdfFile ncfile = NetcdfFiles.open(pathToFile)) { // File is automatically closed when try block exits - + // Find a variable by name Variable v = ncfile.findVariable("temperature"); if (v == null) { System.err.println("Variable not found"); return; } - + // Read all data from the variable Array data = v.read(); - + // Read a subset using section specification // Format: "dim1_start:dim1_end:dim1_stride, dim2_start:dim2_end, ..." Array subset = v.read("0:10:2, :, 5"); - + } catch (IOException e) { e.printStackTrace(); } @@ -81,23 +90,23 @@ try (NetcdfFile ncfile = NetcdfFiles.open(pathToFile)) { System.out.println("Variable: " + var.getFullName()); System.out.println(" Type: " + var.getDataType()); System.out.println(" Shape: " + Arrays.toString(var.getShape())); - + // Get attributes for (Attribute attr : var.attributes()) { System.out.println(" Attribute: " + attr.getFullName() + " = " + attr.getValue()); } } - + // List dimensions for (Dimension dim : ncfile.getDimensions()) { System.out.println("Dimension: " + dim.getFullName() + " = " + dim.getLength()); } - + // Get global attributes for (Attribute attr : ncfile.getGlobalAttributes()) { System.out.println("Global: " + attr.getFullName() + " = " + attr.getValue()); } - + } catch (IOException e) { e.printStackTrace(); } @@ -136,6 +145,7 @@ for (int t = 0; t < shape3d[0]; t++) { ### Array Section Syntax NetCDF-Java uses Fortran 90 array section syntax with zero-based indexing: + - `":"` - all elements in dimension - `"start:end"` - elements from start to end (inclusive) - `"start:end:stride"` - elements with stride @@ -144,6 +154,7 @@ NetCDF-Java uses Fortran 90 array section syntax with zero-based indexing: ## NetCDF Markup Language (NcML) NcML is an XML representation of netCDF metadata that can: + - Describe netCDF file structure (similar to CDL) - Modify existing datasets (add/change attributes, variables) - Create virtual datasets through aggregation @@ -155,7 +166,7 @@ NcML is an XML representation of netCDF metadata that can: - + @@ -166,6 +177,7 @@ NcML is an XML representation of netCDF metadata that can: ### NcML Aggregation NcML supports several aggregation types: + - **joinExisting:** Concatenate along existing dimension - **joinNew:** Create new dimension for aggregation - **union:** Combine variables from multiple files @@ -216,6 +228,7 @@ NetCDF-Java automatically handles compressed files (.Z, .zip, .gzip, .gz, .bz2) ## File Format Support The library can read many formats through IOServiceProvider implementations: + - NetCDF-3 (classic and 64-bit offset) - NetCDF-4 (HDF5-based) - HDF4 and HDF5 @@ -259,6 +272,7 @@ dependencies { ## ToolsUI Application ToolsUI is a graphical application for browsing and debugging NetCDF files: + - Download: `toolsUI.jar` from netCDF-Java downloads page - Run: `java -Xmx1g -jar toolsUI.jar` - Features: Browse metadata, view data, test coordinate systems, debug IOSPs @@ -284,6 +298,7 @@ ToolsUI is a graphical application for browsing and debugging NetCDF files: ## Integration with THREDDS The THREDDS Data Server (TDS) is built on top of netCDF-Java and provides: + - Remote data access via OPeNDAP, WCS, WMS, HTTP - Catalog services for dataset discovery - Aggregation and virtual dataset support @@ -298,6 +313,7 @@ The THREDDS Data Server (TDS) is built on top of netCDF-Java and provides: ## When to Use NetCDF-Java Use netCDF-Java when you need to: + - Read scientific data in Java applications - Support multiple file formats with a single API - Work with remote datasets (OPeNDAP, HTTP, S3) diff --git a/windsurf-harnett/skills/opendap/README.md b/windsurf-harnett/skills/opendap/README.md index a7ac6e507..8bd378fe1 100644 --- a/windsurf-harnett/skills/opendap/README.md +++ b/windsurf-harnett/skills/opendap/README.md @@ -22,6 +22,7 @@ This Windsurf skill provides comprehensive knowledge of OPeNDAP (Open-source Pro ## When to Use Use this skill when: + - Accessing remote scientific data via OPeNDAP URLs - Writing programs that use OPeNDAP services - Constructing constraint expressions for data subsetting diff --git a/windsurf-harnett/skills/opendap/SKILL.md b/windsurf-harnett/skills/opendap/SKILL.md index c4311061e..a26275580 100644 --- a/windsurf-harnett/skills/opendap/SKILL.md +++ b/windsurf-harnett/skills/opendap/SKILL.md @@ -3,8 +3,8 @@ name: opendap description: Understanding OPeNDAP (Open-source Project for a Network Data Access Protocol) for accessing remote scientific data via HTTP, including DAP2/DAP4 protocols, constraint expressions, data models, and client integration. Use when working with OPeNDAP URLs, writing data access code, or integrating with NetCDF. metadata: author: opendap-documentation - version: "1.0" - date: "2026-01-19" + version: '1.0' + date: '2026-01-19' --- # OPeNDAP Skill @@ -16,6 +16,7 @@ This skill provides comprehensive knowledge of OPeNDAP to help you access, serve OPeNDAP (Open-source Project for a Network Data Access Protocol) provides a way for researchers to access scientific data anywhere on the Internet from a wide variety of programs. It uses a client/server architecture built on HTTP and provides flexible data subsetting through constraint expressions. **Key Features**: + - Network-transparent data access via URLs - Data subsetting at the server (reduces bandwidth) - Format-independent data model @@ -33,6 +34,7 @@ OPeNDAP uses a web-based client/server model similar to the World Wide Web: - **Protocol**: HTTP-based Data Access Protocol (DAP) **Data Flow**: + ``` User Program → OPeNDAP Client Library → HTTP Request → OPeNDAP Server ↓ @@ -44,16 +46,19 @@ User Program ← Translated Data ← DAP Response ← Read Local Files An OPeNDAP URL identifies a dataset and optionally includes a constraint expression: **Basic URL Structure**: + ``` http://server.domain/path/to/dataset.nc ``` **URL with Constraint Expression**: + ``` http://server.domain/path/to/dataset.nc?variable[start:stop]&selection_clause ``` **URL Suffixes** (Service Endpoints): + - `.dds` - Dataset Descriptor Structure (DAP2 - data shape) - `.das` - Data Attribute Structure (DAP2 - metadata) - `.dmr.xml` - Dataset Metadata Response (DAP4 - combined structure) @@ -66,11 +71,13 @@ http://server.domain/path/to/dataset.nc?variable[start:stop]&selection_clause ### 3. Data Access Protocol (DAP) **DAP2** (Older, widely supported): + - Separate DDS and DAS responses - Binary data in .dods format - Simpler data model **DAP4** (Newer, enhanced): + - Unified DMR (Dataset Metadata Response) in XML - Enhanced data model with groups - Better support for complex types @@ -81,7 +88,7 @@ http://server.domain/path/to/dataset.nc?variable[start:stop]&selection_clause ### Base Types - **Byte, Int16, Int32, Int64** - Integer types -- **UInt16, UInt32, UInt64** - Unsigned integers +- **UInt16, UInt32, UInt64** - Unsigned integers - **Float32, Float64** - Floating-point numbers - **String** - Character strings - **URL** - Uniform Resource Locators @@ -94,6 +101,7 @@ http://server.domain/path/to/dataset.nc?variable[start:stop]&selection_clause - **Grid** - Array with coordinate map vectors **Example Grid**: + ``` Grid { Array: @@ -121,18 +129,21 @@ URL?projection&selection ### Array Subsetting **Single element**: + ``` ?variable[index] ?sst[0][10][20] ``` **Range (start:stop)**: + ``` ?variable[start:stop] ?sst[0:10][20:30][40:50] ``` **Stride (start:stride:stop)**: + ``` ?variable[start:stride:stop] ?sst[0:2:100] # Every 2nd element from 0 to 100 @@ -143,6 +154,7 @@ URL?projection&selection **Comparison operators**: `<`, `>`, `<=`, `>=`, `=`, `!=` **Examples**: + ``` ?station&station.temp>20.0 ?station&station.lat>0.0&station.lon<-60.0 @@ -154,16 +166,19 @@ URL?projection&selection Hyrax servers support functions for advanced operations: **geogrid()** - Subset by geographic coordinates: + ``` ?geogrid(sst, 62, 206, 56, 210, "19722100 ``` **Multiple conditions**: + ``` ?sequence&sequence.temp>20&sequence.depth<100 ``` @@ -310,6 +333,7 @@ sst = ds.variables['sst'][0:10, 20:30, 40:50] ### Command-line Tools **ncdump** (with OPeNDAP-enabled NetCDF): + ```bash ncdump -h "http://server.org/data.nc" ncdump -v sst "http://server.org/data.nc?sst[0:10][20:30]" @@ -320,20 +344,24 @@ ncdump -v sst "http://server.org/data.nc?sst[0:10][20:30]" ### Common Issues **1. URL not recognized**: + - Ensure NetCDF library is compiled with DAP support - Check URL syntax (http:// or https://) **2. Constraint expression errors**: + - Verify variable names match DDS/DMR exactly - Check array bounds (0-indexed) - Ensure proper quoting in shell commands **3. Performance issues**: + - Use constraint expressions to reduce data transfer - Request only needed variables - Consider server-side functions for processing **4. Authentication**: + - Some servers require credentials - Use .netrc file or URL-embedded credentials - Check server documentation for auth methods diff --git a/windsurf-harnett/skills/opendap/references/CLIENT-USAGE.md b/windsurf-harnett/skills/opendap/references/CLIENT-USAGE.md index bccf24540..746145afa 100644 --- a/windsurf-harnett/skills/opendap/references/CLIENT-USAGE.md +++ b/windsurf-harnett/skills/opendap/references/CLIENT-USAGE.md @@ -15,20 +15,20 @@ int main() { size_t start[3] = {0, 0, 0}; size_t count[3] = {1, 10, 10}; float data[10][10]; - + char *url = "http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz"; - + status = nc_open(url, NC_NOWRITE, &ncid); if (status != NC_NOERR) { fprintf(stderr, "Error opening URL: %s\n", nc_strerror(status)); return 1; } - + status = nc_inq_varid(ncid, "sst", &varid); status = nc_get_vara_float(ncid, varid, start, count, &data[0][0]); - + printf("Data at [0][5][5]: %f\n", data[5][5]); - + nc_close(ncid); return 0; } @@ -63,30 +63,30 @@ for (int i = 0; i < nvars; i++) { program read_opendap use netcdf implicit none - + integer :: ncid, varid, status integer :: start(3), count(3) real :: data(10, 10, 1) character(len=256) :: url - + url = "http://test.opendap.org/dap/data/nc/sst.mnmean.nc.gz" - + status = nf90_open(url, NF90_NOWRITE, ncid) if (status /= NF90_NOERR) then print *, "Error: ", trim(nf90_strerror(status)) stop end if - + status = nf90_inq_varid(ncid, "sst", varid) - + start = [1, 1, 1] count = [10, 10, 1] status = nf90_get_var(ncid, varid, data, start=start, count=count) - + print *, "Sample value: ", data(5, 5, 1) - + status = nf90_close(ncid) - + end program read_opendap ``` @@ -96,26 +96,26 @@ end program read_opendap program time_series use netcdf implicit none - + integer :: ncid, varid, status, nt real, allocatable :: temp(:) character(len=256) :: url - + url = "http://server.org/data.nc?temp[0:1000][45][90]" - + status = nf90_open(url, NF90_NOWRITE, ncid) status = nf90_inq_varid(ncid, "temp", varid) status = nf90_inq_dimlen(ncid, 1, nt) - + allocate(temp(nt)) status = nf90_get_var(ncid, varid, temp) - + print *, "Time series length: ", nt print *, "Mean: ", sum(temp)/nt - + deallocate(temp) status = nf90_close(ncid) - + end program time_series ``` @@ -306,6 +306,7 @@ for var_name in var_names: ### Using .netrc Create `~/.netrc`: + ``` machine server.org login username @@ -313,6 +314,7 @@ password mypassword ``` Set permissions: + ```bash chmod 600 ~/.netrc ``` diff --git a/windsurf-harnett/skills/opendap/references/CONSTRAINTS.md b/windsurf-harnett/skills/opendap/references/CONSTRAINTS.md index f134b004d..202fdc7fa 100644 --- a/windsurf-harnett/skills/opendap/references/CONSTRAINTS.md +++ b/windsurf-harnett/skills/opendap/references/CONSTRAINTS.md @@ -20,21 +20,25 @@ Both parts are optional. Either or both can be used. ### Selecting Variables **Single variable**: + ``` ?temperature ``` **Multiple variables**: + ``` ?temperature,salinity,pressure ``` **Structure fields**: + ``` ?station.latitude,station.longitude,station.time ``` **Nested structures**: + ``` ?cruise.station.cast.temperature ``` @@ -42,22 +46,26 @@ Both parts are optional. Either or both can be used. ### Array Subsetting **Single element**: + ``` ?sst[0][10][20] ``` **Range (start:stop)**: + ``` ?sst[0:10][20:30][40:50] ``` **Stride (start:stride:stop)**: + ``` ?sst[0:2:100][0:5:50][0:10:180] # Every 2nd time, every 5th lat, every 10th lon ``` **Open-ended ranges**: + ``` ?sst[10:] # From index 10 to end ?sst[:100] # From start to index 100 @@ -69,16 +77,19 @@ Both parts are optional. Either or both can be used. When subsetting a Grid, include coordinate variables: **Without coordinates** (just the array): + ``` ?sst[0:10][20:30][40:50] ``` **With coordinates**: + ``` ?time[0:10],lat[20:30],lon[40:50],sst[0:10][20:30][40:50] ``` **Using geogrid() function** (Hyrax servers): + ``` ?geogrid(sst, north_lat, west_lon, south_lat, east_lon) ?geogrid(sst, 62, 206, 56, 210) @@ -89,6 +100,7 @@ When subsetting a Grid, include coordinate variables: ### Comparison Operators **Numeric comparisons**: + ``` ?station&station.temperature>20.0 ?station&station.depth<100 @@ -99,12 +111,14 @@ When subsetting a Grid, include coordinate variables: ``` **String comparisons**: + ``` ?station&station.name="Station_A" ?station&station.type!="reference" ``` **String pattern matching** (regex): + ``` ?station&station.comment~=".*shark.*" ?station&station.location~="^North.*" @@ -113,18 +127,21 @@ When subsetting a Grid, include coordinate variables: ### Multiple Conditions **AND conditions** (multiple & clauses): + ``` ?station&station.lat>0.0&station.lon<-60.0 ?station&station.temp>20&station.depth<50&station.salinity>34 ``` **OR conditions** (using lists): + ``` ?station&station.month={4,5,6,7} ?station&station.type={"CTD","XBT","profiler"} ``` **Combining variables in lists**: + ``` ?station&station.month={4,5,6,station.monsoon_month} ``` @@ -132,12 +149,14 @@ When subsetting a Grid, include coordinate variables: ### Range Conditions **Value between bounds**: + ``` ?station&station.temp>15&station.temp<25 ?data&1019722&data.time<19755 ``` @@ -149,11 +168,13 @@ Sequences are like database tables with rows of data. ### Selecting Fields **Specific fields**: + ``` ?sequence.field1,sequence.field2,sequence.field3 ``` **All fields with filter**: + ``` ?sequence&sequence.temperature>20 ``` @@ -161,17 +182,20 @@ Sequences are like database tables with rows of data. ### Filtering Rows **Single condition**: + ``` ?URI_GSO-Dock.Time,URI_GSO-Dock.Sea_Temp&URI_GSO-Dock.Time<35234.1 ``` **Multiple conditions**: + ``` ?station.cast.press,station.cast.temp&station.cast.press>500.0 ?station.cast&station.cast.temp>22.0 ``` **Complex filters**: + ``` ?station&station.lat>0.0&station.month={4,5,6,7} ``` @@ -181,16 +205,19 @@ Sequences are like database tables with rows of data. ### geogrid() - Geographic Subsetting **Syntax**: + ``` geogrid(variable, top, left, bottom, right, [other_expressions]) ``` **Example**: + ``` ?geogrid(sst, 62, 206, 56, 210, "1972219722 ``` **Multiple variables with conditions**: + ``` ?lat,lon,temp,salinity&temp>20&salinity>34 ``` @@ -243,16 +277,19 @@ linear_scale(variable) # Uses metadata ### Working with Nested Structures **Nested sequence**: + ``` ?cruise.station.cast.depth,cruise.station.cast.temp ``` **Filter on nested field**: + ``` ?cruise.station&cruise.station.latitude>0 ``` **Multiple levels**: + ``` ?cruise.station.cast&cruise.station.cast.temp>20 ``` @@ -260,16 +297,19 @@ linear_scale(variable) # Uses metadata ### Sampling Patterns **Every Nth element**: + ``` ?sst[0:10:1857][0:5:89][0:10:180] ``` **Sparse sampling**: + ``` ?sst[::100][::10][::20] # Every 100th time, 10th lat, 20th lon ``` **Diagonal sampling** (if supported): + ``` ?array[0:10][0:10] # 11x11 subset ``` @@ -323,21 +363,25 @@ linear_scale(variable) # Uses metadata ### Regular Expression Syntax **Wildcards**: + - `.` - Any single character - `.*` - Zero or more characters - `.+` - One or more characters - `.?` - Zero or one character **Anchors**: + - `^` - Start of string - `$` - End of string **Character classes**: + - `[abc]` - Match a, b, or c - `[0-9]` - Match any digit - `[^0-9]` - Match any non-digit **Examples**: + ``` ?station&station.comment~=".*shark.*" # Contains "shark" ?station&station.name~="^Station_[0-9]+$" # Station_123 format @@ -349,24 +393,28 @@ linear_scale(variable) # Uses metadata ### Common Constraint Expression Errors **Invalid variable name**: + ``` Error: Variable 'sst_temp' not found Fix: Check DDS/DMR for correct name ``` **Index out of bounds**: + ``` Error: Array index [2000] exceeds dimension size [1857] Fix: Verify dimension sizes in DDS/DMR ``` **Syntax error**: + ``` Error: Expected ']' but found ',' Fix: Check bracket matching and syntax ``` **Type mismatch**: + ``` Error: Cannot compare String with Float64 Fix: Use appropriate operators for data type @@ -375,17 +423,20 @@ Fix: Use appropriate operators for data type ### Testing Constraint Expressions **Use .ascii for debugging**: + ``` http://server/data.nc.ascii?sst[0:1][0:5][0:5] ``` **Check metadata first**: + ``` http://server/data.nc.dmr.xml http://server/data.nc.dds ``` **Test incrementally**: + 1. Start with simple projection: `?variable` 2. Add subsetting: `?variable[0:10]` 3. Add selection: `?variable[0:10]&variable>100` @@ -395,16 +446,19 @@ http://server/data.nc.dds ### Minimize Data Transfer **Request only needed variables**: + ``` ?temp,salinity # Not ?* ``` **Use appropriate stride**: + ``` ?sst[0:10:1857] # Every 10th instead of all ``` **Subset at server**: + ``` ?sst[0:100][20:40][50:80] # Not full array ``` @@ -412,11 +466,13 @@ http://server/data.nc.dds ### Leverage Server Functions **Process at server**: + ``` ?linear_scale(geogrid(sst, 45, -130, 30, -110)) ``` **Combine operations**: + ``` ?mean(sst[0:100][20:40][50:80]) # If server supports ``` @@ -424,6 +480,7 @@ http://server/data.nc.dds ### Cache Metadata **Reuse DDS/DAS/DMR**: + - Cache structure information - Avoid repeated metadata requests - Use cached info for constraint construction diff --git a/windsurf-harnett/skills/opendap/references/DATA-MODEL.md b/windsurf-harnett/skills/opendap/references/DATA-MODEL.md index 2e4471cd0..f8c55e7d4 100644 --- a/windsurf-harnett/skills/opendap/references/DATA-MODEL.md +++ b/windsurf-harnett/skills/opendap/references/DATA-MODEL.md @@ -13,54 +13,63 @@ Base types represent atomic data values. ### Numeric Types **Byte** (8-bit signed integer): + ``` Byte temperature; Range: -128 to 127 ``` **Int16** (16-bit signed integer): + ``` Int16 elevation; Range: -32,768 to 32,767 ``` **Int32** (32-bit signed integer): + ``` Int32 station_id; Range: -2,147,483,648 to 2,147,483,647 ``` **Int64** (64-bit signed integer, DAP4 only): + ``` Int64 timestamp; Range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ``` **UInt16** (16-bit unsigned integer): + ``` UInt16 count; Range: 0 to 65,535 ``` **UInt32** (32-bit unsigned integer): + ``` UInt32 pixel_value; Range: 0 to 4,294,967,295 ``` **UInt64** (64-bit unsigned integer, DAP4 only): + ``` UInt64 file_size; Range: 0 to 18,446,744,073,709,551,615 ``` **Float32** (32-bit floating point): + ``` Float32 temperature; IEEE 754 single precision ``` **Float64** (64-bit floating point): + ``` Float64 latitude; IEEE 754 double precision @@ -69,12 +78,14 @@ IEEE 754 double precision ### String Types **String** (variable-length character string): + ``` String station_name; String comment; ``` **URL** (Uniform Resource Locator): + ``` URL data_source; ``` @@ -88,11 +99,13 @@ Constructor types build complex data structures from base types and other constr Multi-dimensional arrays of any base or constructor type. **Syntax**: + ``` Type name[dim1][dim2]...[dimN]; ``` **Examples**: + ``` Float32 temperature[time=100][lat=50][lon=80]; Int16 elevation[y=1000][x=1000]; @@ -100,12 +113,14 @@ String station_names[stations=25]; ``` **Characteristics**: + - Fixed dimensions at creation - Homogeneous (all elements same type) - Zero-indexed - Rectangular (not ragged) **Subsetting**: + ``` temperature[0:10][20:30][40:50] # Hyperslab temperature[5][25][60] # Single element @@ -117,6 +132,7 @@ temperature[0:2:100] # With stride Named collection of variables (like a C struct). **Syntax**: + ``` Structure { Type1 field1; @@ -126,6 +142,7 @@ Structure { ``` **Example**: + ``` Structure { Float64 latitude; @@ -136,11 +153,13 @@ Structure { ``` **Characteristics**: + - Heterogeneous (different types) - Named fields - Accessed via dot notation: `station.latitude` **Use Cases**: + - Grouping related metadata - Station information - Coordinate pairs @@ -151,6 +170,7 @@ Structure { Ordered collection of instances (like database rows). **Syntax**: + ``` Sequence { Type1 field1; @@ -160,6 +180,7 @@ Sequence { ``` **Example**: + ``` Sequence { Float64 time; @@ -170,12 +191,14 @@ Sequence { ``` **Characteristics**: + - Variable length (number of instances unknown) - Each instance is a Structure - Can be filtered with selection expressions - Accessed one instance at a time **Nested Sequences**: + ``` Sequence { Int32 station_id; @@ -190,6 +213,7 @@ Sequence { ``` **Filtering**: + ``` ?cast&cast.temperature>20 ?cast.depth,cast.temperature&cast.depth<100 @@ -200,6 +224,7 @@ Sequence { Array with coordinate map vectors (georeferenced data). **Syntax**: + ``` Grid { Array: @@ -212,6 +237,7 @@ Grid { ``` **Example**: + ``` Grid { Array: @@ -224,12 +250,14 @@ Grid { ``` **Characteristics**: + - Combines array with coordinate variables - Maps provide independent variable values - Common for geospatial data - Subsetting returns both array and corresponding maps **Use Cases**: + - Gridded climate data - Satellite imagery - Model output @@ -244,16 +272,17 @@ DAP4 adds several enhancements to the data model. Hierarchical organization of variables (like HDF5 groups). **Example**: + ``` Group { Float64 time[time=100]; - + Group heights { Float32 delta_time[time=100]; Float64 lat_ph[time=100]; Float64 lon_ph[time=100]; } - + Group quality { Int32 qa_flag[time=100]; } @@ -261,6 +290,7 @@ Group { ``` **Access**: + ``` ?/gt3r/heights/lat_ph ?/gt3r/quality/qa_flag @@ -271,11 +301,13 @@ Group { Binary data of unknown structure. **Syntax**: + ``` Opaque name; ``` **Use Cases**: + - Embedded images - Compressed data - Proprietary formats @@ -286,6 +318,7 @@ Opaque name; Named integer constants (like C enum). **Syntax**: + ``` Enum quality_flag { good = 0, @@ -296,6 +329,7 @@ Enum quality_flag { ``` **Usage**: + ``` quality_flag qa[time=100]; ``` @@ -305,12 +339,14 @@ quality_flag qa[time=100]; ### From NetCDF-3 to OPeNDAP **NetCDF-3 Dimensions** → **OPeNDAP Array dimensions**: + ``` NetCDF: float temp(time, lat, lon); OPeNDAP: Float32 temp[time][lat][lon]; ``` **NetCDF-3 Variables** → **OPeNDAP Grid** (if coordinate variables exist): + ``` NetCDF: float sst(time, lat, lon); @@ -328,6 +364,7 @@ OPeNDAP: ``` **NetCDF-3 Attributes** → **OPeNDAP DAS**: + ``` NetCDF: sst:units = "degC"; OPeNDAP DAS: @@ -339,18 +376,21 @@ OPeNDAP DAS: ### From HDF5 to OPeNDAP **HDF5 Groups** → **DAP4 Groups**: + ``` HDF5: /group1/subgroup/dataset DAP4: /group1/subgroup/dataset ``` **HDF5 Datasets** → **OPeNDAP Arrays**: + ``` HDF5: Dataset "temperature" [100][50][80] DAP4: Float32 temperature[100][50][80]; ``` **HDF5 Compound Types** → **OPeNDAP Structures**: + ``` HDF5 Compound: { @@ -370,6 +410,7 @@ OPeNDAP: ### From Relational Database to OPeNDAP **Database Table** → **OPeNDAP Sequence**: + ``` SQL Table: CREATE TABLE stations ( @@ -389,6 +430,7 @@ OPeNDAP: ``` **SQL WHERE** → **OPeNDAP Selection**: + ``` SQL: SELECT * FROM stations WHERE lat > 0; OPeNDAP: ?stations&stations.lat>0 @@ -396,19 +438,19 @@ OPeNDAP: ?stations&stations.lat>0 ## Data Type Sizes -| Type | Size (bytes) | Notes | -|------|--------------|-------| -| Byte | 1 | Signed | -| Int16 | 2 | Signed | -| Int32 | 4 | Signed | -| Int64 | 8 | Signed, DAP4 only | -| UInt16 | 2 | Unsigned | -| UInt32 | 4 | Unsigned | -| UInt64 | 8 | Unsigned, DAP4 only | -| Float32 | 4 | IEEE 754 | -| Float64 | 8 | IEEE 754 | -| String | Variable | Null-terminated | -| URL | Variable | Null-terminated | +| Type | Size (bytes) | Notes | +| ------- | ------------ | ------------------- | +| Byte | 1 | Signed | +| Int16 | 2 | Signed | +| Int32 | 4 | Signed | +| Int64 | 8 | Signed, DAP4 only | +| UInt16 | 2 | Unsigned | +| UInt32 | 4 | Unsigned | +| UInt64 | 8 | Unsigned, DAP4 only | +| Float32 | 4 | IEEE 754 | +| Float64 | 8 | IEEE 754 | +| String | Variable | Null-terminated | +| URL | Variable | Null-terminated | ## Attributes @@ -417,6 +459,7 @@ Attributes provide metadata about variables and datasets. ### Attribute Types All base types can be used as attributes: + ``` String long_name "Sea Surface Temperature"; String units "degrees_C"; @@ -428,6 +471,7 @@ Int32 missing_value -9999; ### Attribute Containers **Variable attributes**: + ``` sst { String long_name "Sea Surface Temperature"; @@ -437,6 +481,7 @@ sst { ``` **Global attributes**: + ``` NC_GLOBAL { String title "COADS 1-degree Enhanced"; @@ -448,6 +493,7 @@ NC_GLOBAL { ### Standard Attributes **CF Conventions**: + - `units` - Physical units - `long_name` - Descriptive name - `standard_name` - CF standard name @@ -492,7 +538,7 @@ Dataset { Float64 lower_right_lon; } bounds; } metadata; - + Grid { Array: UInt16 radiance[line=1000][pixel=1000]; @@ -516,7 +562,7 @@ Dataset { Float32 lat[lat=180]; Float32 lon[lon=360]; } air_temperature; - + Grid { Array: Float32 u_wind[time=365][level=50][lat=180][lon=360]; diff --git a/windsurf-harnett/skills/opendap/references/PROTOCOL.md b/windsurf-harnett/skills/opendap/references/PROTOCOL.md index c16dfd2ed..6e8fcdfa1 100644 --- a/windsurf-harnett/skills/opendap/references/PROTOCOL.md +++ b/windsurf-harnett/skills/opendap/references/PROTOCOL.md @@ -9,11 +9,13 @@ DAP2 is the original OPeNDAP protocol, widely supported and stable. ### DAP2 Responses **Dataset Descriptor Structure (DDS)**: + - Describes the "shape" of the data - C-like syntax showing variables, dimensions, and types - Accessed via `.dds` suffix Example DDS: + ``` Dataset { Grid { @@ -29,11 +31,13 @@ Dataset { ``` **Data Attribute Structure (DAS)**: + - Contains metadata about variables - Includes units, descriptions, valid ranges, etc. - Accessed via `.das` suffix Example DAS: + ``` Attributes { sst { @@ -52,6 +56,7 @@ Attributes { ``` **Binary Data (.dods)**: + - XDR-encoded binary data - Includes DDS followed by data values - Most efficient for data transfer @@ -59,12 +64,14 @@ Attributes { ### DAP2 Data Types **Simple Types**: + - Byte, Int16, Int32 - UInt16, UInt32 - Float32, Float64 - String, URL **Constructor Types**: + - Array - Multi-dimensional arrays - Structure - Named collection of variables - Sequence - Ordered list of structures @@ -73,6 +80,7 @@ Attributes { ### DAP2 Constraint Expression Format **Projection** (what to return): + ``` ?var1,var2,var3 ?structure.field1,structure.field2 @@ -80,6 +88,7 @@ Attributes { ``` **Selection** (filtering): + ``` ?var&var>value ?sequence&sequence.field<100 @@ -92,17 +101,19 @@ DAP4 is the enhanced protocol with improved features and performance. ### DAP4 Responses **Dataset Metadata Response (DMR)**: + - Unified XML document combining structure and attributes - Accessed via `.dmr.xml` suffix - Supports groups (hierarchical organization) Example DMR: + ```xml - + @@ -117,7 +128,7 @@ Example DMR: 0.01 - + @@ -128,6 +139,7 @@ Example DMR: ``` **Binary Data (.dap)**: + - More efficient encoding than DAP2 - Chunked transfer for large datasets - Better compression support @@ -135,16 +147,19 @@ Example DMR: ### DAP4 Enhancements **Groups**: + - Hierarchical organization like HDF5 - Namespace management - Example: `/group1/subgroup/variable` **Enhanced Types**: + - Opaque - Binary blobs - Enum - Enumerated types - 64-bit integers (Int64, UInt64) **Improved Constraint Expressions**: + ``` ?dap4.ce=/variable[0:10] ?dap4.ce=/group/variable&/group/variable>100 @@ -153,12 +168,14 @@ Example DMR: ### DAP4 Constraint Expression Format **Projection with namespace**: + ``` ?dap4.ce=/sst ?dap4.ce=/gt3r/heights/delta_time,/gt3r/heights/lon_ph ``` **Filters**: + ``` ?dap4.ce=/sst[0:100][0:50][0:80] ?dap4.ce=/sequence{field1,field2|field1>100} @@ -166,25 +183,27 @@ Example DMR: ## Protocol Comparison -| Feature | DAP2 | DAP4 | -|---------|------|------| -| Metadata | Separate DDS/DAS | Unified DMR (XML) | -| Groups | No | Yes | -| 64-bit integers | No | Yes | -| Encoding | XDR | More efficient | -| Chunking | Limited | Full support | -| Constraint syntax | Simple | Enhanced | -| Adoption | Universal | Growing | +| Feature | DAP2 | DAP4 | +| ----------------- | ---------------- | ----------------- | +| Metadata | Separate DDS/DAS | Unified DMR (XML) | +| Groups | No | Yes | +| 64-bit integers | No | Yes | +| Encoding | XDR | More efficient | +| Chunking | Limited | Full support | +| Constraint syntax | Simple | Enhanced | +| Adoption | Universal | Growing | ## Choosing DAP Version **Use DAP2 when**: + - Maximum compatibility needed - Working with older servers - Simple data structures - Established workflows **Use DAP4 when**: + - Working with grouped data (HDF5-like) - Need 64-bit integer support - Large dataset performance critical @@ -195,6 +214,7 @@ Example DMR: ### Request Methods **GET requests** for all operations: + ``` GET /path/to/dataset.nc.dds HTTP/1.1 Host: server.domain @@ -212,11 +232,13 @@ Content-Type: text/xml (for DMR) ### Authentication **Basic HTTP Auth**: + ``` http://username:password@server.org/data.nc ``` **.netrc file** (recommended): + ``` machine server.org login username @@ -224,6 +246,7 @@ password mypassword ``` **OAuth/Bearer tokens** (server-dependent): + ``` Authorization: Bearer ``` @@ -233,6 +256,7 @@ Authorization: Bearer ### DAP2 Errors Errors returned as text with HTTP error codes: + ``` Error { code = 404; @@ -243,6 +267,7 @@ Error { ### DAP4 Errors XML error responses: + ```xml Variable not found: xyz @@ -271,22 +296,26 @@ XML error responses: ### Caching Servers may cache: + - Metadata responses (DDS, DAS, DMR) - Constraint expression results - Aggregated datasets Clients should cache: + - Metadata for repeated access - Frequently accessed subsets ### Compression DAP4 supports: + - Chunked transfer encoding - Gzip compression - Server-side compression filters Request compressed responses: + ``` Accept-Encoding: gzip, deflate ``` @@ -296,6 +325,7 @@ Accept-Encoding: gzip, deflate ### Version Detection Query server version: + ``` http://server.org/opendap/version ``` @@ -303,11 +333,13 @@ http://server.org/opendap/version ### Function Discovery List available server functions: + ``` http://server.org/data.nc?version() ``` Get function help: + ``` http://server.org/data.nc?function_name() ``` @@ -315,6 +347,7 @@ http://server.org/data.nc?function_name() ### Service Endpoints Standard endpoints: + - `.dds` - DAP2 structure - `.das` - DAP2 attributes - `.dods` - DAP2 binary data @@ -330,6 +363,7 @@ Standard endpoints: ### Server-Side Processing Some servers support: + - **Aggregation** - Combine multiple files - **Subsetting** - Spatial/temporal subsetting - **Transformation** - Unit conversion, reprojection @@ -338,6 +372,7 @@ Some servers support: ### Custom Functions Servers can implement custom functions: + ``` ?custom_function(variable, param1, param2) ``` diff --git a/windsurf-harnett/workflows/implement.md b/windsurf-harnett/workflows/implement.md index 1e3329344..e7c9d10e8 100644 --- a/windsurf-harnett/workflows/implement.md +++ b/windsurf-harnett/workflows/implement.md @@ -21,7 +21,7 @@ This workflow implements GitHub issues by extracting planning information and fo 2. **Extract Implementation Plan from Issue** - Look for these sections in issue body and comments: - "Implementation Plan", "Implementation Steps", or "Implementation Roadmap" - - "Requirements & Acceptance Criteria" + - "Requirements & Acceptance Criteria" - "Technical Details" or "Technical Approach" - "Dependencies" and "Testing Requirements" - Extract numbered/bulleted task lists with time estimates @@ -84,6 +84,7 @@ This workflow implements GitHub issues by extracting planning information and fo ## Implementation Guidelines ### Code Quality Standards + - Follow existing code style and patterns - Maintain backward compatibility - Use established error handling patterns @@ -91,6 +92,7 @@ This workflow implements GitHub issues by extracting planning information and fo - Add appropriate logging and debugging support ### Testing Requirements + - Unit tests for all new functions - Integration tests for new features - Error handling path testing @@ -98,6 +100,7 @@ This workflow implements GitHub issues by extracting planning information and fo - Performance testing if applicable ### Documentation Standards + - Update API documentation for new functions - Add inline comments for complex logic - Update design documents if architecture changes @@ -120,6 +123,7 @@ This workflow implements GitHub issues by extracting planning information and fo ## Example Implementation Flow For issue 92 (CRS Metadata Extraction): + 1. Fetch issue → Extract 7-step implementation plan 2. Review docs/design.md → Understand GeoTIFF architecture 3. Examine src/geotifffile.c → Locate placeholder code @@ -127,4 +131,4 @@ For issue 92 (CRS Metadata Extraction): 5. Implement Step 2: extract_crs_parameters() → Write function 6. Continue through all steps → Run tests → Update GitHub -Example usage: "/implement 60" or "Implement issue 60" \ No newline at end of file +Example usage: "/implement 60" or "Implement issue 60" diff --git a/windsurf-harnett/workflows/issue.md b/windsurf-harnett/workflows/issue.md index c767fa0ae..11b34e4af 100644 --- a/windsurf-harnett/workflows/issue.md +++ b/windsurf-harnett/workflows/issue.md @@ -23,12 +23,14 @@ This workflow helps refine GitHub issues systematically to clarify requirements, ## Step 1: Issue Analysis & Validation First, thoroughly examine the GitHub issue to understand: + - The problem description and context - Current behavior vs. expected behavior - Any error messages, logs, or screenshots provided - Existing discussion or comments **Validate issue quality:** + - ✓ Issue title is clear and specific - ✓ Problem description is understandable - ✓ Expected behavior is defined @@ -41,6 +43,7 @@ First, thoroughly examine the GitHub issue to understand: ## Step 2: Consult Project Documentation Review the project's documentation to gather architectural context and development guidance: + - **Examine docs/design.md** for system architecture, design patterns, and technical specifications - **Check docs/prd.md** for product requirements, feature specifications, and business requirements - **Review docs/prfaq.md** for frequently asked questions, common issues, and implementation guidance @@ -53,6 +56,7 @@ Review the project's documentation to gather architectural context and developme ## Step 3: Examine Codebase Context **Search for similar implementations:** + - Look for related functionality in `src/`, `include/`, `test/` - Identify existing NC_Dispatch implementations to follow - Review error handling patterns and NetCDF error codes used @@ -60,6 +64,7 @@ Review the project's documentation to gather architectural context and developme - Note build system patterns (CMake, Autotools) **Check GitHub context:** + - Search for related existing issues (open and closed) - Check for relevant pull requests - Note any blocking or related issues @@ -72,6 +77,7 @@ Review the project's documentation to gather architectural context and developme Ask 3-7 targeted questions to better understand the problem and requirements. Focus on: **For bug reports:** + - Reproduction steps and environment details - Expected vs. actual behavior - Error messages or logs @@ -79,6 +85,7 @@ Ask 3-7 targeted questions to better understand the problem and requirements. Fo - Any recent changes that might be related **For feature requests:** + - Specific use cases and workflows - Performance requirements or constraints - Integration with existing functionality @@ -86,6 +93,7 @@ Ask 3-7 targeted questions to better understand the problem and requirements. Fo - Backward compatibility requirements **For documentation issues:** + - Which documentation needs updating - Target audience (users, developers, contributors) - Missing or unclear information @@ -98,12 +106,14 @@ Format questions as multiple choice with a recommended answer when appropriate. ## Step 5: Dependency Analysis **Identify dependencies and conflicts:** + - Check if this issue depends on other open issues - Look for potential conflicts with in-progress work - Identify external dependencies (libraries, systems, teams) - Note any blocking issues or prerequisites **Document dependencies:** + - List dependent issues with issue numbers - Identify any work that must be completed first - Note any conflicts that need resolution @@ -115,28 +125,35 @@ Format questions as multiple choice with a recommended answer when appropriate. Based on the answers provided, generate a structured implementation plan: **Plan Structure:** + ```markdown ## Executive Summary + ## Requirements & Acceptance Criteria + - [ ] - [ ] - [ ] ## Implementation Approach + ## Implementation Steps + 1. - 2. - 3. - ## Dependencies + - Depends on # - - Blocks # - ## Testing Requirements + - [ ] Unit tests for all new or modified functions - [ ] Unit tests for error handling paths - [ ] Integration test for @@ -144,13 +161,16 @@ Based on the answers provided, generate a structured implementation plan: - [ ] Test coverage verification (minimum 80% for new code) ## Risks & Mitigations + - ## Notes + ``` **Quality checks:** + - ✓ Steps are concrete and actionable - ✓ Dependencies are clearly identified - ✓ Testing requirements are specific @@ -166,26 +186,32 @@ Based on the answers provided, generate a structured implementation plan: Create a comprehensive follow-up comment on the GitHub issue that documents the refined requirements and implementation plan. This serves as both a record and a roadmap for future implementation. **Comment structure:** + ```markdown ## Issue Refinement Summary ### Executive Summary + ### Requirements & Acceptance Criteria + - [ ] - [ ] - [ ] ### Implementation Plan + ### Next Steps + ``` **Posting to GitHub:** Use the `gh` command line tool to post the comment to the issue: + ```bash gh issue comment --body "your comment text here" ``` @@ -203,6 +229,7 @@ Review the complete analysis for completeness and accuracy, then append the fina - Success criteria for each milestone **Final validation:** + - ✓ All questions have been answered - ✓ Requirements are clear and testable - ✓ Implementation plan is technically sound diff --git a/windsurf-harnett/workflows/release.md b/windsurf-harnett/workflows/release.md index 149734c1d..03ca43910 100644 --- a/windsurf-harnett/workflows/release.md +++ b/windsurf-harnett/workflows/release.md @@ -6,6 +6,7 @@ auto_execution_mode: 3 Generate professional GitHub release notes for a specific version by extracting information from GitHub issues, pull requests, and project boards, following the established release notes format. ## Prerequisites + 1. Identify the version to document (user will specify, e.g., "0.5.0", "1.0.0") 2. Check `docs/releases/` for the latest release notes format examples (v0.4.0.md is comprehensive) 3. Determine the GitHub milestone or project board associated with this version @@ -14,18 +15,21 @@ Generate professional GitHub release notes for a specific version by extracting ## Steps ### 1. Gather Issues and PRs for Version + Use GitHub MCP tools to collect: + - **Closed issues** in the version milestone (if milestone exists) - **Merged pull requests** associated with the version - **Project board items** (if using GitHub Projects for version tracking) - **Release date**: Use the date of the last merged PR or current date Query strategy: + ``` # Search for issues closed in version mcp0_search_issues: query="milestone:v{VERSION} is:closed" -# Search for merged PRs in version +# Search for merged PRs in version mcp0_search_pull_requests: query="is:merged milestone:v{VERSION}" # Alternative: Search by labels if no milestone @@ -33,7 +37,9 @@ mcp0_search_issues: query="label:v{VERSION} is:closed" ``` ### 2. Categorize Issues by Type + Analyze collected issues and PRs, grouping them by: + - **Features**: New functionality (label: enhancement, feature) - **Bug Fixes**: Resolved issues (label: bug, fix) - **Build System**: CMake, Autotools changes (label: build) @@ -44,22 +50,27 @@ Analyze collected issues and PRs, grouping them by: - **Performance**: Optimizations, benchmarks Extract from each issue: + - Title and issue number - Description/summary - Implementation details from PR description - Related files/components modified ### 3. Review Existing Release Notes Format + Examine the most recent release notes (e.g., `docs/releases/v0.4.0.md`) to understand: + - Section structure and order - Writing style (professional, concise, technical) - How features are organized by category - Level of technical detail expected ### 4. Create Release Notes Structure + Generate a markdown file named `docs/releases/v{VERSION}.md` with these sections: **Required Sections:** + - **Title & Subtitle**: Version number + main theme (derived from most impactful features), followed by `>` blockquote with one-line description - **Highlights**: 4-6 bullet points of major achievements (start with bold emphasis, reference issue numbers) - **Features**: Organized by functional areas with detailed descriptions @@ -76,12 +87,14 @@ Generate a markdown file named `docs/releases/v{VERSION}.md` with these sections - **Breaking Changes**: None or list with migration guidance **Footer Section:** + - **Released**: Date (format: YYYY-MM-DD, use last merged PR date or current date) - **Scope**: List GitHub milestone or project board name - **Issues Closed**: Total count of issues/PRs included - **Contributors**: GitHub usernames of PR authors (if multiple contributors) ### 5. Writing Guidelines + - **Be concise**: Keep total length under 100-120 lines (~1 page when rendered) - **Use technical language**: Include file paths, function names, specific implementations - **Reference issues**: Always include issue/PR numbers in format (#123) @@ -92,7 +105,9 @@ Generate a markdown file named `docs/releases/v{VERSION}.md` with these sections - **Credit contributors**: Mention PR authors when appropriate ### 6. Content Transformation Rules + When converting GitHub issues/PRs to release notes: + - **Issue titles → Feature descriptions**: Expand with context and implementation details from PR - **PR descriptions → Implementation details**: Extract what was built, where in codebase, which files modified - **Labels → Section assignment**: Use issue labels to categorize into appropriate sections @@ -102,7 +117,9 @@ When converting GitHub issues/PRs to release notes: - **Multiple related issues → Single feature**: Combine related issues into cohesive feature descriptions ### 7. Quality Checks + Before finalizing: + - ✅ Version number matches milestone/project (format: v0.X.0 or v1.X.0) - ✅ File name matches version (e.g., `v0.5.0.md` for Version 0.5.0) - ✅ All issue numbers are valid and link correctly (#123 format) @@ -115,6 +132,7 @@ Before finalizing: - ✅ Contributors are credited appropriately ### 8. Finalization + - Save the file to `docs/releases/v{VERSION}.md` - Do NOT commit or push (per user rules) - Provide summary: "Release notes for v{VERSION} generated from {N} issues and {M} PRs" @@ -123,6 +141,7 @@ Before finalizing: ## Example Usage **Scenario 1: Version with Milestone** + ``` User: "Create release notes for version 0.5.0" → Search GitHub for milestone:v0.5.0 issues and PRs @@ -134,6 +153,7 @@ User: "Create release notes for version 0.5.0" ``` **Scenario 2: Version with Label-based Tracking** + ``` User: "Generate release notes for v0.6.0" → Search for label:v0.6.0 closed issues @@ -144,6 +164,7 @@ User: "Generate release notes for v0.6.0" ``` **Scenario 3: Version from Project Board** + ``` User: "Create release notes from project 'Version 1.0 Release'" → Query GitHub project board items @@ -154,10 +175,11 @@ User: "Create release notes from project 'Version 1.0 Release'" ``` ## Notes + - If no milestone/label/project exists for the version, ask user to clarify which issues/PRs to include - If some issues are still open, note them in "Known Limitations" section - Adapt section order based on version content (e.g., skip "Dependencies" if none added) - Use past tense for completed work, present tense for capabilities added - Always verify issue/PR numbers are valid before including in release notes - For versions with many issues (>20), prioritize most impactful features in Highlights -- If PR descriptions are sparse, read the actual code changes to understand implementation details \ No newline at end of file +- If PR descriptions are sparse, read the actual code changes to understand implementation details diff --git a/windsurf-harnett/workflows/review.md b/windsurf-harnett/workflows/review.md index 7fe1ec9c2..114fd6e2b 100644 --- a/windsurf-harnett/workflows/review.md +++ b/windsurf-harnett/workflows/review.md @@ -57,6 +57,7 @@ This workflow performs a comprehensive code review on a pull request and adds re ``` Example: + ``` /review 123 ``` @@ -68,4 +69,4 @@ Example: - Include code references using GitHub's line selection - Review focuses on code quality, not creating separate issues - For critical issues that need separate tracking, suggest creating issues in comments -- Group related issues when appropriate \ No newline at end of file +- Group related issues when appropriate diff --git a/windsurf-harnett/workflows/roadmap.md b/windsurf-harnett/workflows/roadmap.md index 4d0e38b58..ca85ce274 100644 --- a/windsurf-harnett/workflows/roadmap.md +++ b/windsurf-harnett/workflows/roadmap.md @@ -23,12 +23,14 @@ This workflow breaks down a version's work into 1-5 detailed sprint planning doc ## Scope **Do:** + - Analyze the specified version's work and break it into 1-5 sprints - Identify technical gaps and missing requirements across all sprints - Create version-specific GitHub Project with sprint breakdown and task organization - Ensure logical dependency flow between sprints **Don't:** + - Modify other versions not specified - Implement code (save for `/plan` workflow) - Create more than 5 sprints for a version @@ -41,18 +43,22 @@ This workflow breaks down a version's work into 1-5 detailed sprint planning doc ## Project Creation Process ### Naming Convention + Version-specific projects follow the pattern: `NEP Version {version} - {brief description}` **Examples:** + - "NEP Version 1.6.0 - GeoTIFF metadata to CF" - "NEP Version 1.7.0 - GRIB2 write support" - "NEP Version 1.8.0 - Performance optimization" ### Project Structure + Each version-specific project includes: + - **Organization-level project** (not repository-specific) - **Standard fields**: Version, Sprint, Task Status, Priority, Component -- **Field values**: +- **Field values**: - Version: "{version}" for all issues - Sprint: 1-5 for respective sprint issues - Task Status: Backlog → In Progress → Review → Done @@ -61,6 +67,7 @@ Each version-specific project includes: - **Issues**: Main version issue + individual sprint issues ### Implementation Tools + - **GitHub CLI** (`gh project create`) for automated creation - **GitHub API** as fallback for advanced configuration - **Manual creation** option when automation fails @@ -114,6 +121,7 @@ Each version-specific project includes: ### 5. Ask 3-6 numbered clarifying questions **Focus areas:** + - Sprint organization and dependency flow based on user-provided description - Ambiguous requirements or missing technical details from the initial prompt - Error handling strategies and NetCDF error code mapping @@ -123,6 +131,7 @@ Each version-specific project includes: - Integration with native format libraries (NCEPLIBS-g2, libgeotiff, NASA CDF) **Question format:** + - Provide suggested answers, labeled with letters, so questions can be answered with just a letter - Recommend an answer and provide justification for your recommendation - Ask minimum 3 questions, maximum 6 questions @@ -132,6 +141,7 @@ Each version-specific project includes: **Wait for user responses before proceeding** **If answers are incomplete:** + - Ask follow-up questions to clarify - Maximum 2 rounds of questions total @@ -147,6 +157,7 @@ Each version-specific project includes: - Link related Issues/PRs to project items for implementation **Important:** + - **DO NOT** add proposed code changes to project item descriptions - Code implementation should be tracked in separate Issues/PRs linked to project items - If example code is presented by the user, that may be included in item descriptions @@ -154,6 +165,7 @@ Each version-specific project includes: ### 7. Update related documentation (if needed) **Only update if clarifications revealed:** + - Architecture changes → Update `docs/design.md` - Functional requirement changes → Update `docs/prd.md` - NC_Dispatch or UDF handler design notes → Add to `docs/design.md` @@ -163,6 +175,7 @@ Each version-specific project includes: ### 8. Verify completion criteria **Confirm all items are satisfied:** + - ✓ Version work is broken into 1-5 logical sprints - ✓ Each sprint has a coherent theme and clear deliverables - ✓ Dependencies between sprints are documented @@ -183,35 +196,42 @@ Each version-specific project includes: ## Error Handling **If version not found:** + - Report "Version {version} not found in GitHub Project" - List available versions from project - Stop workflow **If version is v1.5.0 or earlier:** + - Report "Historical versions (v1.5.0 and earlier) are not supported" - Stop workflow **If version scope is too large for 5 sprints:** + - Report: "Version {version} work is too complex for 5 sprints" - Suggest breaking into multiple versions or reducing scope - Ask user how to proceed **If version has insufficient work for 1 sprint:** + - Report: "Version {version} has minimal work, consider combining with another version" - Ask user if they want to proceed with a single sprint **If dependencies not met:** + - Report: "Previous version dependencies not satisfied" - List missing dependencies - Ask user if they want to proceed anyway **If GitHub Project creation fails:** + - Report specific GitHub API errors for project creation - Suggest manual project creation with the naming convention "NEP Version {version} - {brief description}" - Provide field configuration details for manual setup - Stop workflow **If GitHub Project operations fail:** + - Report specific GitHub API errors - Suggest manual project updates as fallback - Continue with documentation updates if possible diff --git a/windsurf-harnett/workflows/test.md b/windsurf-harnett/workflows/test.md index 80554fa3f..012cdc031 100644 --- a/windsurf-harnett/workflows/test.md +++ b/windsurf-harnett/workflows/test.md @@ -10,6 +10,7 @@ This workflow runs all C and Fortran tests to validate implementation against is ## Prerequisites Ensure the project is built with both CMake and Autotools build systems: + ```bash cd /home/ed/NEP # CMake build @@ -28,36 +29,48 @@ cd .. ## Steps ### 1. Parse Issue Implementation Plan + - Read the GitHub issue to identify testing requirements from the implementation plan - Extract specific test scenarios mentioned in the issue - Note any special testing requirements (edge cases, error conditions, etc.) ### 2. Run CMake Tests (C tests) + // turbo + ```bash cd /home/ed/NEP/build ctest --output-on-failure ``` + **Expected:** All C tests pass. Tests include LZ4 compression tests and UDF handler tests. ### 3. Run Autotools Tests (C tests) + // turbo + ```bash cd /home/ed/NEP/build_autotools/test ./run_tests.sh ``` + **Expected:** All C tests pass with proper HDF5 plugin path configuration. ### 4. Run Fortran Tests (if built) + // turbo + ```bash cd /home/ed/NEP/build_autotools/ftest ./run_tests.sh ``` + **Expected:** All Fortran tests pass. Tests include nep compression tests. ### 5. Verify Test Coverage Requirements + // turbo + ```bash cd /home/ed/NEP/build # Generate coverage report @@ -66,18 +79,23 @@ gcov -r ../src/*.c ../src/*.h coverage=$(gcov -r ../src/*.c | grep "Lines executed:" | awk '{sum += $3} END {print sum/NR}') echo "Coverage: $coverage%" ``` + **Expected:** Minimum 80% coverage for new/modified code as specified in issue requirements. ### 6. Check for Compiler Warnings + ```bash cd /home/ed/NEP/build make clean make VERBOSE=1 2>&1 | grep -i "warning" ``` + **Expected:** No new warnings introduced. Review any warnings and fix if critical. ### 7. Report Results to GitHub Issue + Post a summary comment on the GitHub issue with test results: + - Test execution status (pass/fail) - Coverage percentage (meets requirements?) - Any warnings found @@ -85,11 +103,11 @@ Post a summary comment on the GitHub issue with test results: ## Success Criteria -✅ **CMake Tests**: All CTest tests pass (LZ4, UDF handlers) -✅ **Autotools C Tests**: All tests in test/run_tests.sh pass -✅ **Fortran Tests**: All tests in ftest/run_tests.sh pass (if Fortran enabled) -✅ **Test Coverage**: Minimum 80% coverage for new/modified code -✅ **Compiler Warnings**: No critical warnings introduced +✅ **CMake Tests**: All CTest tests pass (LZ4, UDF handlers) +✅ **Autotools C Tests**: All tests in test/run_tests.sh pass +✅ **Fortran Tests**: All tests in ftest/run_tests.sh pass (if Fortran enabled) +✅ **Test Coverage**: Minimum 80% coverage for new/modified code +✅ **Compiler Warnings**: No critical warnings introduced ✅ **GitHub Reporting**: Results posted to issue with clear status ## Troubleshooting @@ -100,4 +118,4 @@ Post a summary comment on the GitHub issue with test results: - **Build failures**: Clean build directories and rebuild from scratch - **GRIB2 test failures**: Ensure NCEPLIBS-g2 is installed if GRIB2 UDF handler is enabled -**ALL TESTS MUST PASS, NO TESTS MAY BE LEFT BROKEN** \ No newline at end of file +**ALL TESTS MUST PASS, NO TESTS MAY BE LEFT BROKEN**

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