pedotri 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.
Files changed (43) hide show
  1. pedotri-0.1.0/LICENSE +21 -0
  2. pedotri-0.1.0/PKG-INFO +338 -0
  3. pedotri-0.1.0/README.md +292 -0
  4. pedotri-0.1.0/pyproject.toml +190 -0
  5. pedotri-0.1.0/src/pedotri/__init__.py +70 -0
  6. pedotri-0.1.0/src/pedotri/_data/__init__.py +0 -0
  7. pedotri-0.1.0/src/pedotri/_data/classifications/__init__.py +0 -0
  8. pedotri-0.1.0/src/pedotri/_data/classifications/avery.toml +166 -0
  9. pedotri-0.1.0/src/pedotri/_data/classifications/china.toml +102 -0
  10. pedotri-0.1.0/src/pedotri/_data/classifications/embrapa.toml +81 -0
  11. pedotri-0.1.0/src/pedotri/_data/classifications/fao.toml +57 -0
  12. pedotri-0.1.0/src/pedotri/_data/classifications/geppa.toml +205 -0
  13. pedotri-0.1.0/src/pedotri/_data/classifications/hypres.toml +89 -0
  14. pedotri-0.1.0/src/pedotri/_data/classifications/international.toml +153 -0
  15. pedotri-0.1.0/src/pedotri/_data/classifications/isss.toml +165 -0
  16. pedotri-0.1.0/src/pedotri/_data/classifications/jamagne.toml +191 -0
  17. pedotri-0.1.0/src/pedotri/_data/classifications/ka5.toml +408 -0
  18. pedotri-0.1.0/src/pedotri/_data/classifications/kachinsky.toml +131 -0
  19. pedotri-0.1.0/src/pedotri/_data/classifications/northcote.toml +223 -0
  20. pedotri-0.1.0/src/pedotri/_data/classifications/ptg.toml +100 -0
  21. pedotri-0.1.0/src/pedotri/_data/classifications/usda.toml +165 -0
  22. pedotri-0.1.0/src/pedotri/_pandas_accessor.py +83 -0
  23. pedotri-0.1.0/src/pedotri/_polars_namespace.py +67 -0
  24. pedotri-0.1.0/src/pedotri/_types.py +27 -0
  25. pedotri-0.1.0/src/pedotri/ai.py +591 -0
  26. pedotri-0.1.0/src/pedotri/classifier.py +440 -0
  27. pedotri-0.1.0/src/pedotri/cli.py +215 -0
  28. pedotri-0.1.0/src/pedotri/errors.py +27 -0
  29. pedotri-0.1.0/src/pedotri/geometry.py +142 -0
  30. pedotri-0.1.0/src/pedotri/loader.py +188 -0
  31. pedotri-0.1.0/src/pedotri/mcp_server.py +114 -0
  32. pedotri-0.1.0/src/pedotri/plot/__init__.py +47 -0
  33. pedotri-0.1.0/src/pedotri/plot/mpl.py +333 -0
  34. pedotri-0.1.0/src/pedotri/plot/plotly_backend.py +266 -0
  35. pedotri-0.1.0/src/pedotri/plot/svg.py +514 -0
  36. pedotri-0.1.0/src/pedotri/psd.py +262 -0
  37. pedotri-0.1.0/src/pedotri/ptf/__init__.py +31 -0
  38. pedotri-0.1.0/src/pedotri/ptf/saxton_rawls.py +254 -0
  39. pedotri-0.1.0/src/pedotri/ptf/wosten.py +306 -0
  40. pedotri-0.1.0/src/pedotri/py.typed +0 -0
  41. pedotri-0.1.0/src/pedotri/registry.py +156 -0
  42. pedotri-0.1.0/src/pedotri/schema.py +251 -0
  43. pedotri-0.1.0/src/pedotri/units.py +177 -0
pedotri-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ivan Kolomiets
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
pedotri-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,338 @@
1
+ Metadata-Version: 2.4
2
+ Name: pedotri
3
+ Version: 0.1.0
4
+ Summary: Modern, extensible soil texture classification and pedotransfer functions for Python
5
+ Keywords: soil,texture,classification,agronomy,pedology,agriculture,agritech,pedotransfer,hydrology,USDA,FAO,ISSS,GEPPA,Jamagne,Kachinsky,HYPRES,KA5,Embrapa,Northcote,mcp,llm,claude,agent,tool-use
6
+ Author: Ivan Kolomiets
7
+ Author-email: Ivan Kolomiets <ieee802@yandex.ru>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
20
+ Classifier: Topic :: Scientific/Engineering :: GIS
21
+ Classifier: Topic :: Scientific/Engineering :: Hydrology
22
+ Classifier: Typing :: Typed
23
+ Requires-Dist: numpy>=1.24
24
+ Requires-Dist: matplotlib>=3.7 ; extra == 'all'
25
+ Requires-Dist: plotly>=5.18 ; extra == 'all'
26
+ Requires-Dist: pandas>=2.0 ; extra == 'all'
27
+ Requires-Dist: polars>=0.20 ; extra == 'all'
28
+ Requires-Dist: mcp>=1.0 ; extra == 'all'
29
+ Requires-Dist: matplotlib>=3.7 ; extra == 'matplotlib'
30
+ Requires-Dist: mcp>=1.0 ; extra == 'mcp'
31
+ Requires-Dist: pandas>=2.0 ; extra == 'pandas'
32
+ Requires-Dist: plotly>=5.18 ; extra == 'plotly'
33
+ Requires-Dist: polars>=0.20 ; extra == 'polars'
34
+ Requires-Python: >=3.11
35
+ Project-URL: Homepage, https://github.com/ivanfeanor/pedotri
36
+ Project-URL: Repository, https://github.com/ivanfeanor/pedotri
37
+ Project-URL: Issues, https://github.com/ivanfeanor/pedotri/issues
38
+ Project-URL: Documentation, https://ivanfeanor.github.io/pedotri/
39
+ Provides-Extra: all
40
+ Provides-Extra: matplotlib
41
+ Provides-Extra: mcp
42
+ Provides-Extra: pandas
43
+ Provides-Extra: plotly
44
+ Provides-Extra: polars
45
+ Description-Content-Type: text/markdown
46
+
47
+ # pedotri
48
+
49
+ [![PyPI](https://img.shields.io/pypi/v/pedotri.svg)](https://pypi.org/project/pedotri/)
50
+ [![Python](https://img.shields.io/pypi/pyversions/pedotri.svg)](https://pypi.org/project/pedotri/)
51
+ [![License](https://img.shields.io/pypi/l/pedotri.svg)](https://github.com/ivanfeanor/pedotri/blob/main/LICENSE)
52
+
53
+ Modern, extensible soil texture classification and pedotransfer functions for Python.
54
+
55
+ - **Vectorized** point-in-polygon classification via numpy — fast on large arrays.
56
+ - **Extensible.** Define your own classifications in TOML and load them at runtime, or ship them as plugins via `entry_points`.
57
+ - **Multilingual.** Class names available in English, French, German, Spanish, Russian, and Portuguese, with locale fallback.
58
+ - **Comprehensive.** Built-in classifications from around the world: USDA, FAO, GEPPA/Jamagne, KA5, HYPRES, Kachinsky, Embrapa, and more.
59
+ - **Beyond classification.** Pedotransfer functions (Saxton-Rawls, Wösten / HYPRES), particle-size standard conversions, class hierarchies, distance-to-boundary, and a pure-SVG triangle renderer.
60
+
61
+ ## Install
62
+
63
+ ```bash
64
+ pip install pedotri # core only (numpy)
65
+ pip install "pedotri[matplotlib]" # static plots
66
+ pip install "pedotri[plotly]" # interactive plots
67
+ pip install "pedotri[pandas]" # DataFrame accessor
68
+ pip install "pedotri[polars]" # Polars accessor
69
+ pip install "pedotri[mcp]" # MCP server for Claude Desktop / Cursor
70
+ pip install "pedotri[all]" # everything
71
+ ```
72
+
73
+ ## Quick start
74
+
75
+ ```python
76
+ import pedotri
77
+
78
+ # Single point — returns the class key
79
+ pedotri.classify(13, 50, "USDA")
80
+ # 'clay'
81
+
82
+ # Batch (vectorized)
83
+ pedotri.classify([13, 45, 70], [50, 24, 10], "FAO")
84
+ # ['fine', 'medium', 'coarse']
85
+
86
+ # Localized class names
87
+ pedotri.classify(13, 50, "USDA", locale="fr")
88
+ # 'argile'
89
+ pedotri.classify(13, 50, "GEPPA", locale="fr")
90
+ # 'argile lourde'
91
+
92
+ # 1-D classifications (single axis)
93
+ pedotri.classify(35, "KACHINSKY")
94
+ # 'medium_loam'
95
+
96
+ # Rich result with hierarchy and signed distance to boundary
97
+ result = pedotri.classify(13, 50, "USDA", detailed=True)
98
+ result.key # 'clay'
99
+ result.name # 'clay' (localized name; identical to key for USDA en)
100
+ result.group # 'fine'
101
+ result.distance # positive when strictly inside the class polygon
102
+ ```
103
+
104
+ ## Built-in classifications
105
+
106
+ | Key | Region | Axes | Classes | Reference |
107
+ |-----------------|-----------------|-------------------|---------|----------------------------------------------------|
108
+ | `USDA` | USA (global) | sand, clay | 12 | USDA Soil Survey Manual (Handbook 18, 1993) |
109
+ | `FAO` | International | sand, clay | 3 | Verheye & Ameryckx 1984 (FAO grouping) |
110
+ | `INTERNATIONAL` | International | sand, clay | 11 | Leeper & Uren 1993 |
111
+ | `ISSS` | International | sand, clay | 12 | ISSS / Verheye & Ameryckx 1984 |
112
+ | `AVERY` | UK | sand, clay | 12 | Avery 1980 (Soil Survey of England and Wales) |
113
+ | `JAMAGNE` | France | sand, clay | 13 | Jamagne 1967 (original) |
114
+ | `GEPPA` | France | sand, clay | 14 | Baize & Jamagne 1995 (GEPPA-Aisne refinement) |
115
+ | `HYPRES` | Europe | sand, clay | 5 | Wösten et al. 1999 |
116
+ | `KA5` | Germany | sand, clay | 31 | Bodenkundliche Kartieranleitung 5 (full subdivision) |
117
+ | `PTG` | Poland | sand, clay | 6 | Polskie Towarzystwo Gleboznawcze 2008 |
118
+ | `NORTHCOTE` | Australia | sand, clay | 16 | Northcote 1979 (Factual Key, fine clay subdivisions) |
119
+ | `CHINA` | China | sand, clay | 6 | GB/T 17296-2009 (Chinese national standard) |
120
+ | `EMBRAPA` | Brazil | sand, clay | 5 | Embrapa, SiBCS 5ª ed. 2018 |
121
+ | `KACHINSKY` | Russia / CIS | physical_clay | 9 | Качинский 1965 (1-D classification by <0.01 mm fraction) |
122
+
123
+ ## API for one- and two-axis classifications
124
+
125
+ Most classifications are defined on the sand-silt-clay simplex (2-D); a few — like Kachinsky — are defined on a single axis (e.g. percent particles <0.01 mm). `classify()` adapts to the chosen classification:
126
+
127
+ ```python
128
+ pedotri.classify(70, 20, "USDA") # 2-D positional
129
+ pedotri.classify(10, "KACHINSKY") # 1-D positional
130
+ pedotri.classify(sand=70, clay=20, classification="USDA") # 2-D kwargs
131
+ pedotri.classify(physical_clay=10, classification="KACHINSKY") # 1-D kwargs
132
+ ```
133
+
134
+ ## Custom classifications
135
+
136
+ Define your own classification in TOML and load it:
137
+
138
+ ```python
139
+ pedotri.register_classification("path/to/my_classification.toml")
140
+ pedotri.classify(40, 30, classification="MY_CLASSIFICATION")
141
+ ```
142
+
143
+ See [docs/custom-classifications](https://ivanfeanor.github.io/pedotri/custom/) for the schema.
144
+
145
+ ## Texture diagrams
146
+
147
+ Three backends share the same API and accept the same arguments. Pick whichever fits your output target.
148
+
149
+ ### Built-in SVG (no plotting dependency)
150
+
151
+ ```python
152
+ from pedotri.plot import render_svg, TextureDiagram
153
+
154
+ svg = render_svg("USDA", title="USDA Soil Texture Triangle")
155
+ TextureDiagram("GEPPA", locale="fr").save("geppa.svg")
156
+
157
+ # Inline in Jupyter via _repr_svg_
158
+ TextureDiagram("USDA", points=[(40, 25), (15, 60)], point_labels=["A", "B"])
159
+ ```
160
+
161
+ ### Matplotlib (`pip install pedotri[matplotlib]`)
162
+
163
+ ```python
164
+ from pedotri.plot import render_mpl
165
+
166
+ fig = render_mpl("USDA", points=[(40, 25)], point_labels=["sample"])
167
+ fig.savefig("usda.pdf")
168
+ ```
169
+
170
+ ### Plotly (`pip install pedotri[plotly]`)
171
+
172
+ Uses Plotly's native ternary subplot — full interactivity, hover tooltips, export to interactive HTML.
173
+
174
+ ```python
175
+ from pedotri.plot import render_plotly
176
+
177
+ fig = render_plotly("USDA", points=[(40, 25)], point_labels=["sample"])
178
+ fig.write_html("usda.html")
179
+ ```
180
+
181
+ 1-D classifications (e.g. Kachinsky) render as a horizontal banded axis in all three backends.
182
+
183
+ ## DataFrame accessors
184
+
185
+ When `pandas` or `polars` is installed, `import pedotri` registers a `.soil` accessor on DataFrames:
186
+
187
+ ```python
188
+ import pandas as pd
189
+ import pedotri # registers df.soil
190
+
191
+ df = pd.DataFrame({"sand": [60, 20, 70], "clay": [10, 50, 5]})
192
+ df["texture"] = df.soil.classify("USDA")
193
+
194
+ # Localized names
195
+ df["fr"] = df.soil.classify("GEPPA", locale="fr")
196
+
197
+ # Different column names
198
+ df2 = pd.DataFrame({"S": [60], "C": [10]})
199
+ df2.soil.classify("USDA", columns={"sand": "S", "clay": "C"})
200
+ ```
201
+
202
+ Polars works identically:
203
+
204
+ ```python
205
+ import polars as pl
206
+ df = pl.DataFrame({"sand": [60, 20], "clay": [10, 50]})
207
+ df.with_columns(texture=df.soil.classify("USDA"))
208
+ ```
209
+
210
+ ## Command-line interface
211
+
212
+ ```bash
213
+ pedotri list # list registered classifications
214
+ pedotri info USDA # show details for one
215
+ pedotri classify --sand 60 --clay 20 -c USDA # single sample
216
+ pedotri classify -c KACHINSKY --physical-clay 35 # 1-D
217
+ pedotri classify --csv samples.csv --output out.csv -c USDA # batch
218
+ pedotri render -c USDA --title "USDA" -o usda.svg # write an SVG
219
+ ```
220
+
221
+ ## Use as an LLM tool
222
+
223
+ pedotri ships ready-made JSON tool schemas and an MCP server so models can call it directly.
224
+
225
+ ### Anthropic API / OpenAI tool use
226
+
227
+ ```python
228
+ import pedotri.ai
229
+ from anthropic import Anthropic
230
+
231
+ client = Anthropic()
232
+ response = client.messages.create(
233
+ model="claude-sonnet-4-5",
234
+ max_tokens=1024,
235
+ tools=pedotri.ai.tool_schemas(),
236
+ messages=[{"role": "user", "content": "What's the texture of a 60/20/20 sand/clay/silt sample under USDA?"}],
237
+ )
238
+ # When the model returns tool_use blocks, dispatch them:
239
+ for block in response.content:
240
+ if block.type == "tool_use":
241
+ result = pedotri.ai.run(block.name, block.input)
242
+ # → {"key": "sandy_clay_loam", "name": "sandy clay loam", "group": "moderately_fine", ...}
243
+ ```
244
+
245
+ Eight tools are exposed: `classify_soil`, `classify_soil_1d`, `list_classifications`, `classification_info`, `saxton_rawls`, `wosten`, `convert_particle_size`, `render_diagram`. Errors come back as structured envelopes (not raised) so the model can self-correct in a tool-use loop.
246
+
247
+ ### Claude Desktop (MCP)
248
+
249
+ Install with the MCP extra and add to your Claude Desktop config:
250
+
251
+ ```bash
252
+ pip install "pedotri[mcp]"
253
+ ```
254
+
255
+ On macOS, edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
256
+
257
+ ```json
258
+ {
259
+ "mcpServers": {
260
+ "pedotri": {
261
+ "command": "pedotri-mcp"
262
+ }
263
+ }
264
+ }
265
+ ```
266
+
267
+ Claude will then see all eight pedotri tools in every conversation and can call them while reasoning about soil samples.
268
+
269
+ ## Units
270
+
271
+ **Every fraction input in pedotri is *percent* by default** (0-100 range). Lab reports commonly use g/kg (especially for organic matter) or g/g (mass fraction); pass `units="g/kg"` or `units="g/g"` to convert at the boundary:
272
+
273
+ ```python
274
+ # Same soil sample, three input conventions
275
+ pedotri.classify(60, 20, "USDA") # percent (default)
276
+ pedotri.classify(600, 200, "USDA", units="g/kg") # g/kg
277
+ pedotri.classify(0.6, 0.2, "USDA", units="g/g") # mass fraction
278
+
279
+ # Saxton-Rawls with organic-matter in g/kg
280
+ saxton_rawls(sand=40, clay=20, organic_matter=20, units="g/kg")
281
+ ```
282
+
283
+ The `units=` keyword is accepted by `classify()`, `saxton_rawls()`, `wosten()`, and `psd.convert()`.
284
+
285
+ **Organic carbon vs. organic matter:** PTFs (Saxton-Rawls, Wösten) expect organic *matter*, but most lab reports give organic *carbon*. Convert with `pedotri.units.organic_carbon_to_organic_matter()` (Van Bemmelen factor 1.724) first:
286
+
287
+ ```python
288
+ from pedotri.units import organic_carbon_to_organic_matter
289
+
290
+ oc_pct = 1.16 # from lab report (% organic carbon)
291
+ om_pct = float(organic_carbon_to_organic_matter(oc_pct)[0]) # ~2.0 %
292
+ saxton_rawls(sand=40, clay=20, organic_matter=om_pct)
293
+ ```
294
+
295
+ `pedotri.units` also exposes `g_per_kg_to_percent`, `percent_to_g_per_kg`, `g_per_g_to_percent`, `percent_to_g_per_g`, `organic_matter_to_organic_carbon`, and `to_percent` / `from_percent` for arbitrary unit identifiers.
296
+
297
+ ## Pedotransfer functions
298
+
299
+ Two PTFs ship in `pedotri.ptf`:
300
+
301
+ ### Saxton-Rawls (2006)
302
+
303
+ Water retention + saturated conductivity from sand, clay, and organic matter.
304
+
305
+ ```python
306
+ from pedotri.ptf import saxton_rawls
307
+
308
+ r = saxton_rawls(sand=40, clay=20, organic_matter=2.0)
309
+ r.wilting_point # theta_1500 (m³/m³)
310
+ r.field_capacity # theta_33 (m³/m³)
311
+ r.saturation # theta_s (m³/m³)
312
+ r.available_water # FC - WP
313
+ r.saturated_conductivity # K_s (mm/h)
314
+ r.bulk_density # g/cm³
315
+ r.air_entry_tension # psi_e (kPa)
316
+
317
+ # Compaction-corrected output (Saxton-Rawls 2006 Eq. 6-7)
318
+ r_compacted = saxton_rawls(40, 20, 2.0, density_factor=1.10)
319
+ ```
320
+
321
+ ### Wösten (1999) — HYPRES Mualem-van Genuchten
322
+
323
+ ```python
324
+ from pedotri.ptf import wosten
325
+
326
+ w = wosten(sand=40, silt=40, clay=20, organic_matter=2.0, bulk_density=1.4, topsoil=True)
327
+ w.theta_s # saturated water content
328
+ w.alpha # van Genuchten alpha (1/cm)
329
+ w.n # van Genuchten n
330
+ w.saturated_conductivity # K_s (cm/day)
331
+ w.l # Mualem pore-connectivity parameter
332
+ ```
333
+
334
+ Both functions accept either scalar inputs (returning one result object) or array-like inputs (returning a list).
335
+
336
+ ## License
337
+
338
+ MIT
@@ -0,0 +1,292 @@
1
+ # pedotri
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/pedotri.svg)](https://pypi.org/project/pedotri/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/pedotri.svg)](https://pypi.org/project/pedotri/)
5
+ [![License](https://img.shields.io/pypi/l/pedotri.svg)](https://github.com/ivanfeanor/pedotri/blob/main/LICENSE)
6
+
7
+ Modern, extensible soil texture classification and pedotransfer functions for Python.
8
+
9
+ - **Vectorized** point-in-polygon classification via numpy — fast on large arrays.
10
+ - **Extensible.** Define your own classifications in TOML and load them at runtime, or ship them as plugins via `entry_points`.
11
+ - **Multilingual.** Class names available in English, French, German, Spanish, Russian, and Portuguese, with locale fallback.
12
+ - **Comprehensive.** Built-in classifications from around the world: USDA, FAO, GEPPA/Jamagne, KA5, HYPRES, Kachinsky, Embrapa, and more.
13
+ - **Beyond classification.** Pedotransfer functions (Saxton-Rawls, Wösten / HYPRES), particle-size standard conversions, class hierarchies, distance-to-boundary, and a pure-SVG triangle renderer.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ pip install pedotri # core only (numpy)
19
+ pip install "pedotri[matplotlib]" # static plots
20
+ pip install "pedotri[plotly]" # interactive plots
21
+ pip install "pedotri[pandas]" # DataFrame accessor
22
+ pip install "pedotri[polars]" # Polars accessor
23
+ pip install "pedotri[mcp]" # MCP server for Claude Desktop / Cursor
24
+ pip install "pedotri[all]" # everything
25
+ ```
26
+
27
+ ## Quick start
28
+
29
+ ```python
30
+ import pedotri
31
+
32
+ # Single point — returns the class key
33
+ pedotri.classify(13, 50, "USDA")
34
+ # 'clay'
35
+
36
+ # Batch (vectorized)
37
+ pedotri.classify([13, 45, 70], [50, 24, 10], "FAO")
38
+ # ['fine', 'medium', 'coarse']
39
+
40
+ # Localized class names
41
+ pedotri.classify(13, 50, "USDA", locale="fr")
42
+ # 'argile'
43
+ pedotri.classify(13, 50, "GEPPA", locale="fr")
44
+ # 'argile lourde'
45
+
46
+ # 1-D classifications (single axis)
47
+ pedotri.classify(35, "KACHINSKY")
48
+ # 'medium_loam'
49
+
50
+ # Rich result with hierarchy and signed distance to boundary
51
+ result = pedotri.classify(13, 50, "USDA", detailed=True)
52
+ result.key # 'clay'
53
+ result.name # 'clay' (localized name; identical to key for USDA en)
54
+ result.group # 'fine'
55
+ result.distance # positive when strictly inside the class polygon
56
+ ```
57
+
58
+ ## Built-in classifications
59
+
60
+ | Key | Region | Axes | Classes | Reference |
61
+ |-----------------|-----------------|-------------------|---------|----------------------------------------------------|
62
+ | `USDA` | USA (global) | sand, clay | 12 | USDA Soil Survey Manual (Handbook 18, 1993) |
63
+ | `FAO` | International | sand, clay | 3 | Verheye & Ameryckx 1984 (FAO grouping) |
64
+ | `INTERNATIONAL` | International | sand, clay | 11 | Leeper & Uren 1993 |
65
+ | `ISSS` | International | sand, clay | 12 | ISSS / Verheye & Ameryckx 1984 |
66
+ | `AVERY` | UK | sand, clay | 12 | Avery 1980 (Soil Survey of England and Wales) |
67
+ | `JAMAGNE` | France | sand, clay | 13 | Jamagne 1967 (original) |
68
+ | `GEPPA` | France | sand, clay | 14 | Baize & Jamagne 1995 (GEPPA-Aisne refinement) |
69
+ | `HYPRES` | Europe | sand, clay | 5 | Wösten et al. 1999 |
70
+ | `KA5` | Germany | sand, clay | 31 | Bodenkundliche Kartieranleitung 5 (full subdivision) |
71
+ | `PTG` | Poland | sand, clay | 6 | Polskie Towarzystwo Gleboznawcze 2008 |
72
+ | `NORTHCOTE` | Australia | sand, clay | 16 | Northcote 1979 (Factual Key, fine clay subdivisions) |
73
+ | `CHINA` | China | sand, clay | 6 | GB/T 17296-2009 (Chinese national standard) |
74
+ | `EMBRAPA` | Brazil | sand, clay | 5 | Embrapa, SiBCS 5ª ed. 2018 |
75
+ | `KACHINSKY` | Russia / CIS | physical_clay | 9 | Качинский 1965 (1-D classification by <0.01 mm fraction) |
76
+
77
+ ## API for one- and two-axis classifications
78
+
79
+ Most classifications are defined on the sand-silt-clay simplex (2-D); a few — like Kachinsky — are defined on a single axis (e.g. percent particles <0.01 mm). `classify()` adapts to the chosen classification:
80
+
81
+ ```python
82
+ pedotri.classify(70, 20, "USDA") # 2-D positional
83
+ pedotri.classify(10, "KACHINSKY") # 1-D positional
84
+ pedotri.classify(sand=70, clay=20, classification="USDA") # 2-D kwargs
85
+ pedotri.classify(physical_clay=10, classification="KACHINSKY") # 1-D kwargs
86
+ ```
87
+
88
+ ## Custom classifications
89
+
90
+ Define your own classification in TOML and load it:
91
+
92
+ ```python
93
+ pedotri.register_classification("path/to/my_classification.toml")
94
+ pedotri.classify(40, 30, classification="MY_CLASSIFICATION")
95
+ ```
96
+
97
+ See [docs/custom-classifications](https://ivanfeanor.github.io/pedotri/custom/) for the schema.
98
+
99
+ ## Texture diagrams
100
+
101
+ Three backends share the same API and accept the same arguments. Pick whichever fits your output target.
102
+
103
+ ### Built-in SVG (no plotting dependency)
104
+
105
+ ```python
106
+ from pedotri.plot import render_svg, TextureDiagram
107
+
108
+ svg = render_svg("USDA", title="USDA Soil Texture Triangle")
109
+ TextureDiagram("GEPPA", locale="fr").save("geppa.svg")
110
+
111
+ # Inline in Jupyter via _repr_svg_
112
+ TextureDiagram("USDA", points=[(40, 25), (15, 60)], point_labels=["A", "B"])
113
+ ```
114
+
115
+ ### Matplotlib (`pip install pedotri[matplotlib]`)
116
+
117
+ ```python
118
+ from pedotri.plot import render_mpl
119
+
120
+ fig = render_mpl("USDA", points=[(40, 25)], point_labels=["sample"])
121
+ fig.savefig("usda.pdf")
122
+ ```
123
+
124
+ ### Plotly (`pip install pedotri[plotly]`)
125
+
126
+ Uses Plotly's native ternary subplot — full interactivity, hover tooltips, export to interactive HTML.
127
+
128
+ ```python
129
+ from pedotri.plot import render_plotly
130
+
131
+ fig = render_plotly("USDA", points=[(40, 25)], point_labels=["sample"])
132
+ fig.write_html("usda.html")
133
+ ```
134
+
135
+ 1-D classifications (e.g. Kachinsky) render as a horizontal banded axis in all three backends.
136
+
137
+ ## DataFrame accessors
138
+
139
+ When `pandas` or `polars` is installed, `import pedotri` registers a `.soil` accessor on DataFrames:
140
+
141
+ ```python
142
+ import pandas as pd
143
+ import pedotri # registers df.soil
144
+
145
+ df = pd.DataFrame({"sand": [60, 20, 70], "clay": [10, 50, 5]})
146
+ df["texture"] = df.soil.classify("USDA")
147
+
148
+ # Localized names
149
+ df["fr"] = df.soil.classify("GEPPA", locale="fr")
150
+
151
+ # Different column names
152
+ df2 = pd.DataFrame({"S": [60], "C": [10]})
153
+ df2.soil.classify("USDA", columns={"sand": "S", "clay": "C"})
154
+ ```
155
+
156
+ Polars works identically:
157
+
158
+ ```python
159
+ import polars as pl
160
+ df = pl.DataFrame({"sand": [60, 20], "clay": [10, 50]})
161
+ df.with_columns(texture=df.soil.classify("USDA"))
162
+ ```
163
+
164
+ ## Command-line interface
165
+
166
+ ```bash
167
+ pedotri list # list registered classifications
168
+ pedotri info USDA # show details for one
169
+ pedotri classify --sand 60 --clay 20 -c USDA # single sample
170
+ pedotri classify -c KACHINSKY --physical-clay 35 # 1-D
171
+ pedotri classify --csv samples.csv --output out.csv -c USDA # batch
172
+ pedotri render -c USDA --title "USDA" -o usda.svg # write an SVG
173
+ ```
174
+
175
+ ## Use as an LLM tool
176
+
177
+ pedotri ships ready-made JSON tool schemas and an MCP server so models can call it directly.
178
+
179
+ ### Anthropic API / OpenAI tool use
180
+
181
+ ```python
182
+ import pedotri.ai
183
+ from anthropic import Anthropic
184
+
185
+ client = Anthropic()
186
+ response = client.messages.create(
187
+ model="claude-sonnet-4-5",
188
+ max_tokens=1024,
189
+ tools=pedotri.ai.tool_schemas(),
190
+ messages=[{"role": "user", "content": "What's the texture of a 60/20/20 sand/clay/silt sample under USDA?"}],
191
+ )
192
+ # When the model returns tool_use blocks, dispatch them:
193
+ for block in response.content:
194
+ if block.type == "tool_use":
195
+ result = pedotri.ai.run(block.name, block.input)
196
+ # → {"key": "sandy_clay_loam", "name": "sandy clay loam", "group": "moderately_fine", ...}
197
+ ```
198
+
199
+ Eight tools are exposed: `classify_soil`, `classify_soil_1d`, `list_classifications`, `classification_info`, `saxton_rawls`, `wosten`, `convert_particle_size`, `render_diagram`. Errors come back as structured envelopes (not raised) so the model can self-correct in a tool-use loop.
200
+
201
+ ### Claude Desktop (MCP)
202
+
203
+ Install with the MCP extra and add to your Claude Desktop config:
204
+
205
+ ```bash
206
+ pip install "pedotri[mcp]"
207
+ ```
208
+
209
+ On macOS, edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
210
+
211
+ ```json
212
+ {
213
+ "mcpServers": {
214
+ "pedotri": {
215
+ "command": "pedotri-mcp"
216
+ }
217
+ }
218
+ }
219
+ ```
220
+
221
+ Claude will then see all eight pedotri tools in every conversation and can call them while reasoning about soil samples.
222
+
223
+ ## Units
224
+
225
+ **Every fraction input in pedotri is *percent* by default** (0-100 range). Lab reports commonly use g/kg (especially for organic matter) or g/g (mass fraction); pass `units="g/kg"` or `units="g/g"` to convert at the boundary:
226
+
227
+ ```python
228
+ # Same soil sample, three input conventions
229
+ pedotri.classify(60, 20, "USDA") # percent (default)
230
+ pedotri.classify(600, 200, "USDA", units="g/kg") # g/kg
231
+ pedotri.classify(0.6, 0.2, "USDA", units="g/g") # mass fraction
232
+
233
+ # Saxton-Rawls with organic-matter in g/kg
234
+ saxton_rawls(sand=40, clay=20, organic_matter=20, units="g/kg")
235
+ ```
236
+
237
+ The `units=` keyword is accepted by `classify()`, `saxton_rawls()`, `wosten()`, and `psd.convert()`.
238
+
239
+ **Organic carbon vs. organic matter:** PTFs (Saxton-Rawls, Wösten) expect organic *matter*, but most lab reports give organic *carbon*. Convert with `pedotri.units.organic_carbon_to_organic_matter()` (Van Bemmelen factor 1.724) first:
240
+
241
+ ```python
242
+ from pedotri.units import organic_carbon_to_organic_matter
243
+
244
+ oc_pct = 1.16 # from lab report (% organic carbon)
245
+ om_pct = float(organic_carbon_to_organic_matter(oc_pct)[0]) # ~2.0 %
246
+ saxton_rawls(sand=40, clay=20, organic_matter=om_pct)
247
+ ```
248
+
249
+ `pedotri.units` also exposes `g_per_kg_to_percent`, `percent_to_g_per_kg`, `g_per_g_to_percent`, `percent_to_g_per_g`, `organic_matter_to_organic_carbon`, and `to_percent` / `from_percent` for arbitrary unit identifiers.
250
+
251
+ ## Pedotransfer functions
252
+
253
+ Two PTFs ship in `pedotri.ptf`:
254
+
255
+ ### Saxton-Rawls (2006)
256
+
257
+ Water retention + saturated conductivity from sand, clay, and organic matter.
258
+
259
+ ```python
260
+ from pedotri.ptf import saxton_rawls
261
+
262
+ r = saxton_rawls(sand=40, clay=20, organic_matter=2.0)
263
+ r.wilting_point # theta_1500 (m³/m³)
264
+ r.field_capacity # theta_33 (m³/m³)
265
+ r.saturation # theta_s (m³/m³)
266
+ r.available_water # FC - WP
267
+ r.saturated_conductivity # K_s (mm/h)
268
+ r.bulk_density # g/cm³
269
+ r.air_entry_tension # psi_e (kPa)
270
+
271
+ # Compaction-corrected output (Saxton-Rawls 2006 Eq. 6-7)
272
+ r_compacted = saxton_rawls(40, 20, 2.0, density_factor=1.10)
273
+ ```
274
+
275
+ ### Wösten (1999) — HYPRES Mualem-van Genuchten
276
+
277
+ ```python
278
+ from pedotri.ptf import wosten
279
+
280
+ w = wosten(sand=40, silt=40, clay=20, organic_matter=2.0, bulk_density=1.4, topsoil=True)
281
+ w.theta_s # saturated water content
282
+ w.alpha # van Genuchten alpha (1/cm)
283
+ w.n # van Genuchten n
284
+ w.saturated_conductivity # K_s (cm/day)
285
+ w.l # Mualem pore-connectivity parameter
286
+ ```
287
+
288
+ Both functions accept either scalar inputs (returning one result object) or array-like inputs (returning a list).
289
+
290
+ ## License
291
+
292
+ MIT