arcat 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
arcat-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: arcat
3
+ Version: 0.1.0
4
+ Summary: Atmospheric River Categorization Toolkit
5
+ Author: Kwesi Twentwewa Quagraine
6
+ Author-email: Kwesi Twentwewa Quagraine <kwsquagraine@gmail.com>
7
+ License: MIT
8
+ Keywords: atmospheric rivers,ivt,weather,extremes,python
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: numpy
15
+ Requires-Dist: xarray
16
+ Requires-Dist: dask
17
+ Requires-Dist: numba
18
+ Requires-Dist: tqdm
19
+
20
+ # ARCat: Atmospheric River Categorization Toolkit
21
+
22
+ **ARCat** is a Python package for **categorizing Atmospheric River (AR) events** based on Integrated Vapor Transport (IVT) data. It provides:
23
+
24
+ - **Event-based AR categorization**: Collapses each AR event to its peak intensity.
25
+ - **Evolution-based AR categorization**: Preserves the intensity evolution within AR events.
26
+
27
+ Additionally, ARCat computes:
28
+
29
+ - **Cumulative IVT** per AR event.
30
+ - **IVT-only values** for each AR event.
31
+ - **Duration-only values** (number of timesteps per event).
32
+
33
+ It is fully compatible with **NumPy arrays**, **xarray**, and **Dask**, allowing analysis of large datasets efficiently.
34
+
35
+ ---
36
+
37
+ ## Features
38
+
39
+ - Event-based and evolution-based AR categorization.
40
+ - Automatic handling of duration rules:
41
+ - Events < 1 day → downgrade category
42
+ - Events 1–2 days → unchanged
43
+ - Events ≥ 2 days → upgrade category
44
+ - Maximum category enforcement.
45
+ - Option to extract IVT-only and duration-only arrays.
46
+ - Easy integration with **xarray/Dask** for large datasets.
47
+ - Fully documented and tested, with CI support.
48
+
49
+ ---
50
+
51
+ ## Installation
52
+
53
+ Clone the repository and install locally:
54
+
55
+ ```bash
56
+ git clone https://github.com/yourusername/arcat.git
57
+ cd arcat
58
+ pip install -e .
59
+ ````
60
+ ## Quick Start
61
+
62
+ ### Import the package
63
+
64
+ ```python
65
+ import numpy as np
66
+ from arcat.core import AR_categorization_scheme, AR_categorization_evolution_scheme
67
+ ```
68
+
69
+ ### Prepare your IVT array
70
+
71
+ ```python
72
+ # Example: random IVT values for demonstration
73
+ ivt = np.random.rand(20) * 1500 # IVT in kg/m/s
74
+ ```
75
+
76
+ ### Event-based AR categorization
77
+
78
+ ```python
79
+ # Run the event-based AR scheme
80
+ final_cat, cum_ivt, ivt_event, duration_event = AR_categorization_scheme(
81
+ ivt,
82
+ time_resolution_hours=6, # Data resolution in hours
83
+ bin_width=250.0, # IVT bin size
84
+ max_category=6 # Maximum AR category
85
+ )
86
+
87
+ print("Categorized AR events:", final_cat)
88
+ print("Cumulative IVT:", cum_ivt)
89
+ print("IVT-only values:", ivt_event)
90
+ print("Duration per timestep:", duration_event)
91
+ ```
92
+
93
+ ### Evolution-based AR categorization
94
+
95
+ ```python
96
+ # Run the evolution-based AR scheme
97
+ final_cat, cum_ivt, ivt_event, duration_event = AR_categorization_evolution_scheme(
98
+ ivt,
99
+ time_resolution_hours=6, # Data resolution in hours
100
+ bin_width=250.0, # IVT bin size
101
+ max_category=6 # Maximum AR category
102
+ )
103
+
104
+ print("Evolution categories:", final_cat)
105
+ print("Cumulative IVT:", cum_ivt)
106
+ print("IVT-only values:", ivt_event)
107
+ print("Duration per timestep:", duration_event)
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Parameters
113
+
114
+ | Parameter | Description |
115
+ | ----------------------- | ------------------------------------------------------- |
116
+ | `ivt_array` | 1D NumPy array of IVT values (kg/m/s) |
117
+ | `time_resolution_hours` | Temporal resolution of your data in hours (default: 6) |
118
+ | `bin_width` | Width of each IVT bin for categorization (default: 250) |
119
+ | `max_category` | Maximum AR category (default: 6) |
120
+
121
+ ---
122
+
123
+ ## Returns
124
+
125
+ 1. `final_categories`: Array of AR categories (1–6) after duration adjustments.
126
+ 2. `cumulative_ivt`: Cumulative IVT during AR events.
127
+ 3. `ivt_event`: Original IVT values during AR events (0 elsewhere).
128
+ 4. `duration_event`: Number of timesteps per AR event (0 elsewhere).
129
+
130
+ ---
131
+
132
+ ## Usage Notes
133
+
134
+ * Event-based scheme collapses each AR event to its maximum intensity (`max_bounded_replace`).
135
+ * Evolution-based scheme preserves temporal evolution of IVT within AR events.
136
+ * Duration rules are applied **per continuous AR segment**.
137
+ * Works with any **NumPy array**, and can be integrated with **xarray or Dask arrays** for large datasets.
138
+
139
+ ---
140
+ ## Adopting for xarray data
141
+ ```python
142
+ final_cat, cum_ivt, ivt_event, duration_event = xr.apply_ufunc(AR_categorization_scheme,
143
+ Ivt_ds['IVT'].as_numpy(),
144
+ input_core_dims = [['time']],
145
+ output_core_dims = [['time'],['time'],['time'],['time']],
146
+ vectorize=True, # Auto-vectorize over lat/lon
147
+ dask='parallelized', # Enable parallelization using Dask
148
+ output_dtypes=[np.int8,np.float32,np.float32, np.int8])
149
+
150
+ ```
151
+
152
+ ## Contributing
153
+
154
+ We welcome contributions! Please follow these steps:
155
+
156
+ 1. Fork the repository
157
+ 2. Create a new branch: `git checkout -b feature-name`
158
+ 3. Make your changes
159
+ 4. Run tests
160
+ 5. Submit a pull request
161
+
162
+ ---
163
+
164
+ ## License
165
+
166
+ This project is licensed under the [Creative Commons License](https://web.archive.org/web/20230202010104/https://creativecommons.org/licenses/by/4.0/).
167
+
168
+ ---
169
+
170
+ ## References
171
+
172
+ * Ralph, F. M., Rutz, J. J., Cordeira, J. M., Dettinger, M., Anderson, M., Reynolds, D., ... & Smallcomb, C. (2019). A scale to characterize the strength and impacts of atmospheric rivers. Bulletin of the American Meteorological Society, 100(2), 269-289.
173
+ * Visit the [Atmospheric River Tracking Model Intercomparison Project (ARTMIP)](https://ncar.github.io/ARTMIP/intro.html) to follow Atmospheric River community research and publications.
arcat-0.1.0/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # ARCat: Atmospheric River Categorization Toolkit
2
+
3
+ **ARCat** is a Python package for **categorizing Atmospheric River (AR) events** based on Integrated Vapor Transport (IVT) data. It provides:
4
+
5
+ - **Event-based AR categorization**: Collapses each AR event to its peak intensity.
6
+ - **Evolution-based AR categorization**: Preserves the intensity evolution within AR events.
7
+
8
+ Additionally, ARCat computes:
9
+
10
+ - **Cumulative IVT** per AR event.
11
+ - **IVT-only values** for each AR event.
12
+ - **Duration-only values** (number of timesteps per event).
13
+
14
+ It is fully compatible with **NumPy arrays**, **xarray**, and **Dask**, allowing analysis of large datasets efficiently.
15
+
16
+ ---
17
+
18
+ ## Features
19
+
20
+ - Event-based and evolution-based AR categorization.
21
+ - Automatic handling of duration rules:
22
+ - Events < 1 day → downgrade category
23
+ - Events 1–2 days → unchanged
24
+ - Events ≥ 2 days → upgrade category
25
+ - Maximum category enforcement.
26
+ - Option to extract IVT-only and duration-only arrays.
27
+ - Easy integration with **xarray/Dask** for large datasets.
28
+ - Fully documented and tested, with CI support.
29
+
30
+ ---
31
+
32
+ ## Installation
33
+
34
+ Clone the repository and install locally:
35
+
36
+ ```bash
37
+ git clone https://github.com/yourusername/arcat.git
38
+ cd arcat
39
+ pip install -e .
40
+ ````
41
+ ## Quick Start
42
+
43
+ ### Import the package
44
+
45
+ ```python
46
+ import numpy as np
47
+ from arcat.core import AR_categorization_scheme, AR_categorization_evolution_scheme
48
+ ```
49
+
50
+ ### Prepare your IVT array
51
+
52
+ ```python
53
+ # Example: random IVT values for demonstration
54
+ ivt = np.random.rand(20) * 1500 # IVT in kg/m/s
55
+ ```
56
+
57
+ ### Event-based AR categorization
58
+
59
+ ```python
60
+ # Run the event-based AR scheme
61
+ final_cat, cum_ivt, ivt_event, duration_event = AR_categorization_scheme(
62
+ ivt,
63
+ time_resolution_hours=6, # Data resolution in hours
64
+ bin_width=250.0, # IVT bin size
65
+ max_category=6 # Maximum AR category
66
+ )
67
+
68
+ print("Categorized AR events:", final_cat)
69
+ print("Cumulative IVT:", cum_ivt)
70
+ print("IVT-only values:", ivt_event)
71
+ print("Duration per timestep:", duration_event)
72
+ ```
73
+
74
+ ### Evolution-based AR categorization
75
+
76
+ ```python
77
+ # Run the evolution-based AR scheme
78
+ final_cat, cum_ivt, ivt_event, duration_event = AR_categorization_evolution_scheme(
79
+ ivt,
80
+ time_resolution_hours=6, # Data resolution in hours
81
+ bin_width=250.0, # IVT bin size
82
+ max_category=6 # Maximum AR category
83
+ )
84
+
85
+ print("Evolution categories:", final_cat)
86
+ print("Cumulative IVT:", cum_ivt)
87
+ print("IVT-only values:", ivt_event)
88
+ print("Duration per timestep:", duration_event)
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Parameters
94
+
95
+ | Parameter | Description |
96
+ | ----------------------- | ------------------------------------------------------- |
97
+ | `ivt_array` | 1D NumPy array of IVT values (kg/m/s) |
98
+ | `time_resolution_hours` | Temporal resolution of your data in hours (default: 6) |
99
+ | `bin_width` | Width of each IVT bin for categorization (default: 250) |
100
+ | `max_category` | Maximum AR category (default: 6) |
101
+
102
+ ---
103
+
104
+ ## Returns
105
+
106
+ 1. `final_categories`: Array of AR categories (1–6) after duration adjustments.
107
+ 2. `cumulative_ivt`: Cumulative IVT during AR events.
108
+ 3. `ivt_event`: Original IVT values during AR events (0 elsewhere).
109
+ 4. `duration_event`: Number of timesteps per AR event (0 elsewhere).
110
+
111
+ ---
112
+
113
+ ## Usage Notes
114
+
115
+ * Event-based scheme collapses each AR event to its maximum intensity (`max_bounded_replace`).
116
+ * Evolution-based scheme preserves temporal evolution of IVT within AR events.
117
+ * Duration rules are applied **per continuous AR segment**.
118
+ * Works with any **NumPy array**, and can be integrated with **xarray or Dask arrays** for large datasets.
119
+
120
+ ---
121
+ ## Adopting for xarray data
122
+ ```python
123
+ final_cat, cum_ivt, ivt_event, duration_event = xr.apply_ufunc(AR_categorization_scheme,
124
+ Ivt_ds['IVT'].as_numpy(),
125
+ input_core_dims = [['time']],
126
+ output_core_dims = [['time'],['time'],['time'],['time']],
127
+ vectorize=True, # Auto-vectorize over lat/lon
128
+ dask='parallelized', # Enable parallelization using Dask
129
+ output_dtypes=[np.int8,np.float32,np.float32, np.int8])
130
+
131
+ ```
132
+
133
+ ## Contributing
134
+
135
+ We welcome contributions! Please follow these steps:
136
+
137
+ 1. Fork the repository
138
+ 2. Create a new branch: `git checkout -b feature-name`
139
+ 3. Make your changes
140
+ 4. Run tests
141
+ 5. Submit a pull request
142
+
143
+ ---
144
+
145
+ ## License
146
+
147
+ This project is licensed under the [Creative Commons License](https://web.archive.org/web/20230202010104/https://creativecommons.org/licenses/by/4.0/).
148
+
149
+ ---
150
+
151
+ ## References
152
+
153
+ * Ralph, F. M., Rutz, J. J., Cordeira, J. M., Dettinger, M., Anderson, M., Reynolds, D., ... & Smallcomb, C. (2019). A scale to characterize the strength and impacts of atmospheric rivers. Bulletin of the American Meteorological Society, 100(2), 269-289.
154
+ * Visit the [Atmospheric River Tracking Model Intercomparison Project (ARTMIP)](https://ncar.github.io/ARTMIP/intro.html) to follow Atmospheric River community research and publications.
@@ -0,0 +1,14 @@
1
+ """
2
+ arcat: Atmospheric River Categorization Toolkit
3
+
4
+ Provides:
5
+ - Event-based AR categorization
6
+ - Evolution-based AR categorization
7
+ - xarray + Dask support
8
+ """
9
+
10
+ from .version import __version__
11
+ from .core import (
12
+ AR_categorization_scheme,
13
+ AR_categorization_evolution_scheme,
14
+ )
@@ -0,0 +1,190 @@
1
+ """
2
+ Core AR categorization algorithms for the arcat package.
3
+
4
+ This module contains:
5
+ - Event-based AR categorization (collapses to peak intensity)
6
+ - Evolution-based AR categorization (keeps intensity evolution)
7
+ - IVT-only and duration-only outputs for detailed analysis
8
+ """
9
+
10
+ import numpy as np # Import NumPy for array operations
11
+ from .utils import ( # Import helper functions from utils
12
+ compute_steps_per_day, # Converts time resolution (hours) to timesteps per day
13
+ bin_ivt, # Converts IVT values into AR categories
14
+ cumulative_reset, # Computes cumulative IVT resetting at zeros
15
+ max_bounded_replace # Collapses nonzero segments to their maximum value
16
+ )
17
+
18
+ # ------------------------
19
+ # Internal helper functions
20
+ # ------------------------
21
+
22
+ def _adjust_segment(arr, start, end, duration, steps_per_day):
23
+ """
24
+ Apply duration adjustment rule to a segment (in-place modification).
25
+
26
+ Parameters
27
+ ----------
28
+ arr : np.ndarray
29
+ Array of AR categories to modify.
30
+ start : int
31
+ Start index of the event segment.
32
+ end : int
33
+ End index of the event segment.
34
+ duration : int
35
+ Duration of the segment in timesteps.
36
+ steps_per_day : int
37
+ Number of timesteps that correspond to 1 day.
38
+ """
39
+
40
+ # If duration is shorter than 1 day, downgrade the category by 1
41
+ if duration < steps_per_day:
42
+ arr[start:end] -= 1 # Subtract 1 from all elements in the segment
43
+
44
+ # If duration is longer than or equal to 2 days, upgrade the category by 1
45
+ elif duration >= 2 * steps_per_day:
46
+ arr[start:end] += 1 # Add 1 to all elements in the segment
47
+
48
+ # Otherwise (duration 1–2 days), do nothing (leave category unchanged)
49
+
50
+ # ------------------------
51
+ def _apply_duration_rule(categories, steps_per_day, max_category):
52
+ """
53
+ Apply the duration adjustment to continuous AR events.
54
+
55
+ Parameters
56
+ ----------
57
+ categories : np.ndarray
58
+ Array of initial AR categories per timestep.
59
+ steps_per_day : int
60
+ Number of timesteps corresponding to 1 day.
61
+ max_category : int
62
+ Maximum AR category allowed.
63
+
64
+ Returns
65
+ -------
66
+ final : np.ndarray
67
+ Array of AR categories after duration adjustment.
68
+ duration_arr : np.ndarray
69
+ Array of duration (in timesteps) for each AR event at each index.
70
+ """
71
+
72
+ final = categories.copy() # Copy categories to avoid modifying input
73
+ n = len(categories) # Total number of timesteps
74
+ start = None # Initialize start index of current event
75
+ duration_arr = np.zeros_like(categories, dtype=float) # Store duration per timestep
76
+
77
+ # Loop over each timestep to find AR events
78
+ for i in range(n):
79
+ if categories[i] == 0: # Found a zero → end of AR event
80
+ if start is not None: # There was an ongoing event
81
+ end = i # End index of the event
82
+ duration = end - start # Compute duration in timesteps
83
+ _adjust_segment(final, start, end, duration, steps_per_day) # Apply duration rule
84
+ duration_arr[start:end] = duration # Store duration for all timesteps in event
85
+ start = None # Reset start for next event
86
+ else:
87
+ if start is None: # Found start of a new AR event
88
+ start = i # Mark start index
89
+
90
+ # Handle event that goes until the last timestep
91
+ if start is not None:
92
+ end = n # End index is last timestep
93
+ duration = end - start # Duration of the event
94
+ _adjust_segment(final, start, end, duration, steps_per_day) # Apply duration rule
95
+ duration_arr[start:end] = duration # Store duration
96
+
97
+ # Ensure all categories are within allowed bounds
98
+ return np.clip(final, 0, max_category), duration_arr # Return adjusted categories and duration array
99
+
100
+ # ------------------------
101
+ # Event-based AR categorization
102
+ # ------------------------
103
+
104
+ def AR_categorization_scheme(
105
+ ivt_array: np.ndarray, # Input IVT values
106
+ time_resolution_hours: int = 6, # Time resolution of data in hours
107
+ bin_width: float = 250.0, # Width of each IVT bin for categorization
108
+ max_category: int = 6 # Maximum AR category
109
+ ):
110
+ """
111
+ Event-based AR categorization (collapses each event to peak intensity).
112
+
113
+ Returns:
114
+ final_categories : np.ndarray -> categorized AR values
115
+ cumulative_ivt : np.ndarray -> cumulative IVT over AR events
116
+ ivt_event : np.ndarray -> IVT values only during AR events
117
+ duration_event : np.ndarray -> duration in timesteps for each AR event
118
+ """
119
+
120
+ # Compute how many timesteps correspond to 1 day
121
+ steps_per_day = compute_steps_per_day(time_resolution_hours)
122
+
123
+ # Copy IVT array to preserve original
124
+ original_ivt = ivt_array.copy()
125
+
126
+ # Bin IVT values into categories
127
+ categories = bin_ivt(ivt_array, bin_width, max_category)
128
+
129
+ # Collapse each nonzero event segment to its maximum value
130
+ categories = max_bounded_replace(categories)
131
+
132
+ # Apply duration rule to event segments and get duration per timestep
133
+ final_categories, duration_event = _apply_duration_rule(categories, steps_per_day, max_category)
134
+
135
+ # Create mask of where AR events occur (category > 0)
136
+ mask = final_categories > 0
137
+
138
+ # Compute cumulative IVT over AR events
139
+ cumulative_ivt = cumulative_reset(original_ivt, mask)
140
+
141
+ # Extract IVT values only during AR events, zero elsewhere
142
+ ivt_event = np.where(mask, original_ivt, 0)
143
+
144
+ # Return final categories, cumulative IVT, IVT-only, duration-only arrays
145
+ return final_categories, cumulative_ivt, ivt_event, duration_event
146
+
147
+ # ------------------------
148
+ # Evolution-based AR categorization
149
+ # ------------------------
150
+
151
+ def AR_categorization_evolution_scheme(
152
+ ivt_array: np.ndarray, # Input IVT values
153
+ time_resolution_hours: int = 6, # Time resolution of data in hours
154
+ bin_width: float = 250.0, # IVT bin width
155
+ max_category: int = 6 # Maximum AR category
156
+ ):
157
+ """
158
+ Evolution-based AR categorization (keeps intensity evolution inside each event).
159
+
160
+ Returns:
161
+ final_categories : np.ndarray -> categorized AR values
162
+ cumulative_ivt : np.ndarray -> cumulative IVT over AR events
163
+ ivt_event : np.ndarray -> IVT values only during AR events
164
+ duration_event : np.ndarray -> duration in timesteps for each AR event
165
+ """
166
+
167
+ # Compute timesteps per day
168
+ steps_per_day = compute_steps_per_day(time_resolution_hours)
169
+
170
+ # Copy IVT array to preserve original
171
+ original_ivt = ivt_array.copy()
172
+
173
+ # Bin IVT values into categories
174
+ categories = bin_ivt(ivt_array, bin_width, max_category)
175
+
176
+ # No collapsing for evolution scheme
177
+ # Apply duration rule to event segments and get duration per timestep
178
+ final_categories, duration_event = _apply_duration_rule(categories, steps_per_day, max_category)
179
+
180
+ # Mask of AR events
181
+ mask = final_categories > 0
182
+
183
+ # Compute cumulative IVT over AR events
184
+ cumulative_ivt = cumulative_reset(original_ivt, mask)
185
+
186
+ # Extract IVT values only during AR events, zero elsewhere
187
+ ivt_event = np.where(mask, original_ivt, 0)
188
+
189
+ # Return final categories, cumulative IVT, IVT-only, duration-only arrays
190
+ return final_categories, cumulative_ivt, ivt_event, duration_event
@@ -0,0 +1,131 @@
1
+ """
2
+ Core AR categorization algorithms.
3
+
4
+ Contains:
5
+ - Event-based scheme
6
+ - Evolution-based scheme
7
+ """
8
+
9
+ import numpy as np
10
+ from .utils import (
11
+ compute_steps_per_day,
12
+ bin_ivt,
13
+ cumulative_reset,
14
+ max_bounded_replace,
15
+ )
16
+
17
+
18
+ def _adjust_segment(arr, start, end, duration, steps_per_day):
19
+ """
20
+ Apply duration adjustment rule to a segment.
21
+
22
+ Rules:
23
+ - < 1 day -> downgrade
24
+ - 1–2 days -> unchanged
25
+ - >= 2 days -> upgrade
26
+ """
27
+ if duration < steps_per_day:
28
+ arr[start:end] -= 1
29
+ elif duration >= 2 * steps_per_day:
30
+ arr[start:end] += 1
31
+
32
+
33
+ def _apply_duration_rule(categories, steps_per_day, max_category):
34
+ """
35
+ Apply duration adjustment to continuous AR events.
36
+
37
+ Events are defined as consecutive timesteps
38
+ where category > 0.
39
+ """
40
+ final = categories.copy()
41
+ n = len(categories)
42
+ start = None
43
+
44
+ for i in range(n):
45
+ if categories[i] == 0:
46
+ if start is not None:
47
+ end = i
48
+ duration = end - start
49
+ _adjust_segment(final, start, end, duration, steps_per_day)
50
+ start = None
51
+ else:
52
+ if start is None:
53
+ start = i
54
+
55
+ if start is not None:
56
+ end = n
57
+ duration = end - start
58
+ _adjust_segment(final, start, end, duration, steps_per_day)
59
+
60
+ return np.clip(final, 0, max_category)
61
+
62
+
63
+ # ============================================================
64
+ # EVENT-BASED SCHEME
65
+ # ============================================================
66
+
67
+ def AR_categorization_scheme(
68
+ ivt_array: np.ndarray,
69
+ time_resolution_hours: int = 6,
70
+ bin_width: float = 250.0,
71
+ max_category: int = 6,
72
+ ):
73
+ """
74
+ Event-based AR categorization.
75
+
76
+ Steps:
77
+ 1. Bin IVT into categories
78
+ 2. Collapse each AR event to peak intensity
79
+ 3. Apply duration rule to event
80
+ 4. Compute cumulative IVT
81
+ """
82
+
83
+ steps_per_day = compute_steps_per_day(time_resolution_hours)
84
+
85
+ original_ivt = ivt_array.copy()
86
+
87
+ categories = bin_ivt(ivt_array, bin_width, max_category)
88
+ categories = max_bounded_replace(categories)
89
+
90
+ final_categories = _apply_duration_rule(
91
+ categories, steps_per_day, max_category
92
+ )
93
+
94
+ mask = final_categories > 0
95
+ cumulative_ivt = cumulative_reset(original_ivt, mask)
96
+
97
+ return final_categories, cumulative_ivt
98
+
99
+
100
+ # ============================================================
101
+ # EVOLUTION-BASED SCHEME
102
+ # ============================================================
103
+
104
+ def AR_categorization_evolution_scheme(
105
+ ivt_array: np.ndarray,
106
+ time_resolution_hours: int = 6,
107
+ bin_width: float = 250.0,
108
+ max_category: int = 6,
109
+ ):
110
+ """
111
+ Evolution-based AR categorization.
112
+
113
+ Same as event-based scheme except:
114
+ - Does NOT collapse event to peak intensity.
115
+ - Duration rule still applied event-wise.
116
+ """
117
+
118
+ steps_per_day = compute_steps_per_day(time_resolution_hours)
119
+
120
+ original_ivt = ivt_array.copy()
121
+
122
+ categories = bin_ivt(ivt_array, bin_width, max_category)
123
+
124
+ final_categories = _apply_duration_rule(
125
+ categories, steps_per_day, max_category
126
+ )
127
+
128
+ mask = final_categories > 0
129
+ cumulative_ivt = cumulative_reset(original_ivt, mask)
130
+
131
+ return final_categories, cumulative_ivt
@@ -0,0 +1,86 @@
1
+ """
2
+ Utility functions for AR categorization.
3
+ """
4
+
5
+ import numpy as np
6
+
7
+
8
+ def compute_steps_per_day(time_resolution_hours: int) -> int:
9
+ """
10
+ Compute number of timesteps per day.
11
+
12
+ Parameters
13
+ ----------
14
+ time_resolution_hours : int
15
+ Temporal resolution of input data in hours.
16
+
17
+ Returns
18
+ -------
19
+ int
20
+ Number of timesteps corresponding to 1 day.
21
+ """
22
+ return int(24 / time_resolution_hours)
23
+
24
+
25
+ def bin_ivt(ivt_array: np.ndarray, bin_width: float, max_category: int):
26
+ """
27
+ Convert IVT values to AR categories.
28
+
29
+ IVT is divided into bins of size `bin_width`.
30
+
31
+ Example:
32
+ 0–249 -> 0
33
+ 250–499 -> 1
34
+ etc.
35
+ """
36
+ bins = np.floor(ivt_array / bin_width)
37
+ bins = np.clip(bins, 0, max_category)
38
+ return bins.astype(int)
39
+
40
+
41
+ def cumulative_reset(values: np.ndarray, mask: np.ndarray):
42
+ """
43
+ Compute cumulative sum that resets when mask == 0.
44
+
45
+ Used to compute cumulative IVT per AR event.
46
+ """
47
+ result = np.zeros_like(values, dtype=float)
48
+ running_sum = 0.0
49
+
50
+ for i in range(len(values)):
51
+ if mask[i] == 0:
52
+ running_sum = 0.0
53
+ else:
54
+ running_sum += values[i]
55
+ result[i] = running_sum
56
+
57
+ return result
58
+
59
+
60
+ def max_bounded_replace(arr: np.ndarray):
61
+ """
62
+ Replace each nonzero segment by its maximum value.
63
+
64
+ Example:
65
+ [1,2,3,0,2,1] -> [3,3,3,0,2,2]
66
+
67
+ Used in event-based scheme to collapse AR event
68
+ to peak intensity.
69
+ """
70
+ arr = arr.copy()
71
+ n = len(arr)
72
+ start = None
73
+
74
+ for i in range(n):
75
+ if arr[i] == 0:
76
+ if start is not None:
77
+ arr[start:i] = np.max(arr[start:i])
78
+ start = None
79
+ else:
80
+ if start is None:
81
+ start = i
82
+
83
+ if start is not None:
84
+ arr[start:n] = np.max(arr[start:n])
85
+
86
+ return arr
@@ -0,0 +1,8 @@
1
+ """
2
+ Version file for arcat package.
3
+
4
+ Version follows Semantic Versioning:
5
+ MAJOR.MINOR.PATCH
6
+ """
7
+
8
+ __version__ = "0.1.0"
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: arcat
3
+ Version: 0.1.0
4
+ Summary: Atmospheric River Categorization Toolkit
5
+ Author: Kwesi Twentwewa Quagraine
6
+ Author-email: Kwesi Twentwewa Quagraine <kwsquagraine@gmail.com>
7
+ License: MIT
8
+ Keywords: atmospheric rivers,ivt,weather,extremes,python
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: numpy
15
+ Requires-Dist: xarray
16
+ Requires-Dist: dask
17
+ Requires-Dist: numba
18
+ Requires-Dist: tqdm
19
+
20
+ # ARCat: Atmospheric River Categorization Toolkit
21
+
22
+ **ARCat** is a Python package for **categorizing Atmospheric River (AR) events** based on Integrated Vapor Transport (IVT) data. It provides:
23
+
24
+ - **Event-based AR categorization**: Collapses each AR event to its peak intensity.
25
+ - **Evolution-based AR categorization**: Preserves the intensity evolution within AR events.
26
+
27
+ Additionally, ARCat computes:
28
+
29
+ - **Cumulative IVT** per AR event.
30
+ - **IVT-only values** for each AR event.
31
+ - **Duration-only values** (number of timesteps per event).
32
+
33
+ It is fully compatible with **NumPy arrays**, **xarray**, and **Dask**, allowing analysis of large datasets efficiently.
34
+
35
+ ---
36
+
37
+ ## Features
38
+
39
+ - Event-based and evolution-based AR categorization.
40
+ - Automatic handling of duration rules:
41
+ - Events < 1 day → downgrade category
42
+ - Events 1–2 days → unchanged
43
+ - Events ≥ 2 days → upgrade category
44
+ - Maximum category enforcement.
45
+ - Option to extract IVT-only and duration-only arrays.
46
+ - Easy integration with **xarray/Dask** for large datasets.
47
+ - Fully documented and tested, with CI support.
48
+
49
+ ---
50
+
51
+ ## Installation
52
+
53
+ Clone the repository and install locally:
54
+
55
+ ```bash
56
+ git clone https://github.com/yourusername/arcat.git
57
+ cd arcat
58
+ pip install -e .
59
+ ````
60
+ ## Quick Start
61
+
62
+ ### Import the package
63
+
64
+ ```python
65
+ import numpy as np
66
+ from arcat.core import AR_categorization_scheme, AR_categorization_evolution_scheme
67
+ ```
68
+
69
+ ### Prepare your IVT array
70
+
71
+ ```python
72
+ # Example: random IVT values for demonstration
73
+ ivt = np.random.rand(20) * 1500 # IVT in kg/m/s
74
+ ```
75
+
76
+ ### Event-based AR categorization
77
+
78
+ ```python
79
+ # Run the event-based AR scheme
80
+ final_cat, cum_ivt, ivt_event, duration_event = AR_categorization_scheme(
81
+ ivt,
82
+ time_resolution_hours=6, # Data resolution in hours
83
+ bin_width=250.0, # IVT bin size
84
+ max_category=6 # Maximum AR category
85
+ )
86
+
87
+ print("Categorized AR events:", final_cat)
88
+ print("Cumulative IVT:", cum_ivt)
89
+ print("IVT-only values:", ivt_event)
90
+ print("Duration per timestep:", duration_event)
91
+ ```
92
+
93
+ ### Evolution-based AR categorization
94
+
95
+ ```python
96
+ # Run the evolution-based AR scheme
97
+ final_cat, cum_ivt, ivt_event, duration_event = AR_categorization_evolution_scheme(
98
+ ivt,
99
+ time_resolution_hours=6, # Data resolution in hours
100
+ bin_width=250.0, # IVT bin size
101
+ max_category=6 # Maximum AR category
102
+ )
103
+
104
+ print("Evolution categories:", final_cat)
105
+ print("Cumulative IVT:", cum_ivt)
106
+ print("IVT-only values:", ivt_event)
107
+ print("Duration per timestep:", duration_event)
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Parameters
113
+
114
+ | Parameter | Description |
115
+ | ----------------------- | ------------------------------------------------------- |
116
+ | `ivt_array` | 1D NumPy array of IVT values (kg/m/s) |
117
+ | `time_resolution_hours` | Temporal resolution of your data in hours (default: 6) |
118
+ | `bin_width` | Width of each IVT bin for categorization (default: 250) |
119
+ | `max_category` | Maximum AR category (default: 6) |
120
+
121
+ ---
122
+
123
+ ## Returns
124
+
125
+ 1. `final_categories`: Array of AR categories (1–6) after duration adjustments.
126
+ 2. `cumulative_ivt`: Cumulative IVT during AR events.
127
+ 3. `ivt_event`: Original IVT values during AR events (0 elsewhere).
128
+ 4. `duration_event`: Number of timesteps per AR event (0 elsewhere).
129
+
130
+ ---
131
+
132
+ ## Usage Notes
133
+
134
+ * Event-based scheme collapses each AR event to its maximum intensity (`max_bounded_replace`).
135
+ * Evolution-based scheme preserves temporal evolution of IVT within AR events.
136
+ * Duration rules are applied **per continuous AR segment**.
137
+ * Works with any **NumPy array**, and can be integrated with **xarray or Dask arrays** for large datasets.
138
+
139
+ ---
140
+ ## Adopting for xarray data
141
+ ```python
142
+ final_cat, cum_ivt, ivt_event, duration_event = xr.apply_ufunc(AR_categorization_scheme,
143
+ Ivt_ds['IVT'].as_numpy(),
144
+ input_core_dims = [['time']],
145
+ output_core_dims = [['time'],['time'],['time'],['time']],
146
+ vectorize=True, # Auto-vectorize over lat/lon
147
+ dask='parallelized', # Enable parallelization using Dask
148
+ output_dtypes=[np.int8,np.float32,np.float32, np.int8])
149
+
150
+ ```
151
+
152
+ ## Contributing
153
+
154
+ We welcome contributions! Please follow these steps:
155
+
156
+ 1. Fork the repository
157
+ 2. Create a new branch: `git checkout -b feature-name`
158
+ 3. Make your changes
159
+ 4. Run tests
160
+ 5. Submit a pull request
161
+
162
+ ---
163
+
164
+ ## License
165
+
166
+ This project is licensed under the [Creative Commons License](https://web.archive.org/web/20230202010104/https://creativecommons.org/licenses/by/4.0/).
167
+
168
+ ---
169
+
170
+ ## References
171
+
172
+ * Ralph, F. M., Rutz, J. J., Cordeira, J. M., Dettinger, M., Anderson, M., Reynolds, D., ... & Smallcomb, C. (2019). A scale to characterize the strength and impacts of atmospheric rivers. Bulletin of the American Meteorological Society, 100(2), 269-289.
173
+ * Visit the [Atmospheric River Tracking Model Intercomparison Project (ARTMIP)](https://ncar.github.io/ARTMIP/intro.html) to follow Atmospheric River community research and publications.
@@ -0,0 +1,14 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.cfg
4
+ arcat/__init__.py
5
+ arcat/core.py
6
+ arcat/core_v1.py
7
+ arcat/utils.py
8
+ arcat/version.py
9
+ arcat.egg-info/PKG-INFO
10
+ arcat.egg-info/SOURCES.txt
11
+ arcat.egg-info/dependency_links.txt
12
+ arcat.egg-info/requires.txt
13
+ arcat.egg-info/top_level.txt
14
+ tests/test_core.py
@@ -0,0 +1,5 @@
1
+ numpy
2
+ xarray
3
+ dask
4
+ numba
5
+ tqdm
@@ -0,0 +1 @@
1
+ arcat
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "arcat"
7
+ version = "0.1.0"
8
+ description = "Atmospheric River Categorization Toolkit"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "Kwesi Twentwewa Quagraine", email = "kwsquagraine@gmail.com" }
14
+ ]
15
+ keywords = ["atmospheric rivers", "ivt", "weather","extremes", "python"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent"
20
+ ]
21
+
22
+ dependencies = [
23
+ "numpy",
24
+ "xarray",
25
+ "dask",
26
+ "numba",
27
+ "tqdm"
28
+ ]
arcat-0.1.0/setup.cfg ADDED
@@ -0,0 +1,28 @@
1
+ [metadata]
2
+ name = arcat
3
+ version = 0.1.0
4
+ description = Atmospheric River Categorization Toolkit
5
+ long_description = file: README.md
6
+ long_description_content_type = text/markdown
7
+ author = Kwesi Twentwewa Quagraine
8
+ license = MIT
9
+ keywords = atmospheric rivers, ivt, climate
10
+ classifiers =
11
+ Programming Language :: Python :: 3
12
+ License :: OSI Approved :: MIT License
13
+ Operating System:: OS Independent
14
+
15
+ [options]
16
+ packages = find:
17
+ python_requires = >=3.8
18
+ install_requires =
19
+ numpy
20
+ xarray
21
+ dask
22
+ numba
23
+ tqdm
24
+
25
+ [egg_info]
26
+ tag_build =
27
+ tag_date = 0
28
+
@@ -0,0 +1,28 @@
1
+ import numpy as np
2
+ from arcat.core import AR_categorization_scheme, AR_categorization_evolution_scheme
3
+
4
+
5
+ def test_short_event_downgrade():
6
+ ivt = np.array([300, 300, 0])
7
+ final_cat, cum_ivt, ivt_event, duration_event = AR_categorization_scheme(
8
+
9
+ ivt,
10
+ time_resolution_hours=6, # Data resolution in hours
11
+ bin_width=250.0, # IVT bin size
12
+ max_category=6 # Maximum AR category
13
+ )
14
+
15
+ assert np.all(final_cat[:2] == 0)
16
+
17
+ # Run the event-based AR scheme
18
+ def test_long_event_upgrade():
19
+ ivt = np.array([800] * 12)
20
+ # Run the evolution-based AR scheme
21
+ final_cat, cum_ivt, ivt_event, duration_event = AR_categorization_evolution_scheme(
22
+ ivt,
23
+ time_resolution_hours=6, # Data resolution in hours
24
+ bin_width=250.0, # IVT bin size
25
+ max_category=6 # Maximum AR category
26
+ )
27
+
28
+ assert np.all(final_cat >= 3)