paddle 1.0.1__tar.gz → 1.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 (37) hide show
  1. paddle-1.1.0/.gitignore +14 -0
  2. paddle-1.1.0/PKG-INFO +37 -0
  3. paddle-1.1.0/README.md +8 -0
  4. paddle-1.1.0/pyproject.toml +47 -0
  5. paddle-1.1.0/src/paddle/__init__.py +6 -0
  6. paddle-1.1.0/src/paddle/crm.py +76 -0
  7. paddle-1.1.0/src/paddle/data/saturn1d.yaml +88 -0
  8. paddle-1.1.0/src/paddle/evolve_kinetics.py +40 -0
  9. paddle-1.1.0/src/paddle/find_init_params.py +72 -0
  10. paddle-1.1.0/src/paddle/setup_profile.py +283 -0
  11. paddle-1.1.0/src/paddle/write_profile.py +109 -0
  12. paddle-1.1.0/tests/test_saturn_adiabat.py +54 -0
  13. paddle-1.0.1/CHANGES.txt +0 -11
  14. paddle-1.0.1/LICENSE +0 -10
  15. paddle-1.0.1/MANIFEST.in +0 -4
  16. paddle-1.0.1/PKG-INFO +0 -21
  17. paddle-1.0.1/README.txt +0 -25
  18. paddle-1.0.1/paddle/__init__.py +0 -10
  19. paddle-1.0.1/paddle/common.py +0 -317
  20. paddle-1.0.1/paddle/dual.py +0 -632
  21. paddle-1.0.1/paddle/examples/exp_MNIST.py +0 -195
  22. paddle-1.0.1/paddle/examples/experiment_BSD.py +0 -209
  23. paddle-1.0.1/paddle/examples/experiment_MNIST.py +0 -159
  24. paddle-1.0.1/paddle/examples/experiment_synthetic.py +0 -260
  25. paddle-1.0.1/paddle/examples/lena_std.png +0 -0
  26. paddle-1.0.1/paddle/examples/oneimage.py +0 -48
  27. paddle-1.0.1/paddle/examples/oneimagergb.py +0 -53
  28. paddle-1.0.1/paddle/examples/recovery.py +0 -48
  29. paddle-1.0.1/paddle/ntframes.py +0 -56
  30. paddle-1.0.1/paddle/tests/test_common.py +0 -74
  31. paddle-1.0.1/paddle/tight.py +0 -497
  32. paddle-1.0.1/paddle.egg-info/PKG-INFO +0 -21
  33. paddle-1.0.1/paddle.egg-info/SOURCES.txt +0 -24
  34. paddle-1.0.1/paddle.egg-info/dependency_links.txt +0 -1
  35. paddle-1.0.1/paddle.egg-info/top_level.txt +0 -2
  36. paddle-1.0.1/setup.cfg +0 -8
  37. paddle-1.0.1/setup.py +0 -29
@@ -0,0 +1,14 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .build/
6
+ dist/
7
+ build/
8
+ .coverage
9
+ htmlcov/
10
+ .pytest_cache/
11
+ .venv/
12
+ .env
13
+ *.log
14
+
paddle-1.1.0/PKG-INFO ADDED
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: paddle
3
+ Version: 1.1.0
4
+ Summary: Canoe's utility subroutines
5
+ Project-URL: Homepage, https://github.com/chengcli/paddle
6
+ Project-URL: Repository, https://github.com/chengcli/paddle
7
+ Project-URL: Issues, https://github.com/chengcli/paddle/issues
8
+ Author-email: Cheng Li <chengcli@umich.edu>
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
20
+ Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
21
+ Classifier: Topic :: Scientific/Engineering :: Physics
22
+ Requires-Python: >=3.9
23
+ Requires-Dist: kintera>=1.0.1
24
+ Requires-Dist: snapy>=0.7.0
25
+ Requires-Dist: torch<=2.7.1,>=2.7.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # paddle
31
+
32
+ A minimal, utility subroutines for canoe
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install paddle
paddle-1.1.0/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # paddle
2
+
3
+ A minimal, utility subroutines for canoe
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install paddle
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+ #build-backend = "setuptools.build_meta"
5
+
6
+ [project]
7
+ name = "paddle"
8
+ version = "1.1.0"
9
+ description = "Canoe's utility subroutines"
10
+ readme = "README.md"
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "Cheng Li", email = "chengcli@umich.edu" }]
13
+ keywords = []
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Science/Research",
17
+ "Programming Language :: Python",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Scientific/Engineering :: Atmospheric Science",
26
+ "Topic :: Scientific/Engineering :: Physics",
27
+ "Topic :: Scientific/Engineering :: Astronomy",
28
+ ]
29
+
30
+ dependencies = [
31
+ "torch>=2.7.0,<=2.7.1",
32
+ "kintera>=1.0.1",
33
+ "snapy>=0.7.0",
34
+ ]
35
+
36
+ [project.optional-dependencies]
37
+ dev = [
38
+ "pytest>=7",
39
+ ]
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/chengcli/paddle"
43
+ Repository = "https://github.com/chengcli/paddle"
44
+ Issues = "https://github.com/chengcli/paddle/issues"
45
+
46
+ [project.scripts]
47
+ paddle = "paddle.__main__:main"
@@ -0,0 +1,6 @@
1
+ from .setup_profile import setup_profile
2
+ from .write_profile import write_profile
3
+ from .find_init_params import find_init_params
4
+
5
+ __all__ = ["setup_profile", "write_profile", "find_init_params"]
6
+ __version__ = "1.1.0"
@@ -0,0 +1,76 @@
1
+ import torch
2
+ import math
3
+ import time
4
+ import kintera
5
+ import numpy as np
6
+ from snapy import (
7
+ index,
8
+ MeshBlockOptions,
9
+ MeshBlock,
10
+ OutputOptions,
11
+ NetcdfOutput,
12
+ )
13
+ from kintera import (
14
+ ThermoOptions,
15
+ ThermoX,
16
+ KineticsOptions,
17
+ Kinetics,
18
+ )
19
+
20
+ if __name__ == '__main__':
21
+ # input file
22
+ infile = "earth.yaml"
23
+ device = "cpu"
24
+
25
+ # create meshblock
26
+ op_block = MeshBlockOptions.from_yaml(infile)
27
+ block = MeshBlock(op_block)
28
+ block.to(torch.device(device))
29
+
30
+ # create thermo module
31
+ op_thermo = ThermoOptions.from_yaml(infile)
32
+ thermo_x = ThermoX(op_thermo)
33
+ thermo_x.to(torch.device(device))
34
+
35
+ # create kinetics module
36
+ op_kinet = KineticsOptions.from_yaml(infile)
37
+ kinet = Kinetics(op_kinet)
38
+ kinet.to(torch.device(device))
39
+
40
+ # create output fields
41
+ op_out = OutputOptions().file_basename("earth")
42
+ out2 = NetcdfOutput(op_out.fid(2).variable("prim"))
43
+ out3 = NetcdfOutput(op_out.fid(3).variable("uov"))
44
+ out4 = NetcdfOutput(op_out.fid(4).variable("diag"))
45
+ outs = [out2, out4]
46
+
47
+ # set up initial condition
48
+ w = setup_initial_condition(block, thermo_x)
49
+ print("w = ", w[:,0,0,:])
50
+
51
+ # integration
52
+ current_time = 0.0
53
+ count = 0
54
+ start_time = time.time()
55
+ interior = block.part((0, 0, 0))
56
+ while not block.intg.stop(count, current_time):
57
+ dt = block.max_time_step()
58
+ u = block.buffer("hydro.eos.U")
59
+
60
+ if count % 1 == 0:
61
+ print(f"count = {count}, dt = {dt}, time = {current_time}")
62
+ print("mass = ", u[interior][index.idn].sum())
63
+
64
+ for out in outs:
65
+ out.increment_file_number()
66
+ out.write_output_file(block, current_time)
67
+ out.combine_blocks()
68
+
69
+ for stage in range(len(block.intg.stages)):
70
+ block.forward(dt, stage)
71
+
72
+ # evolve kinetics
73
+ u[index.icy:] += evolve_kinetics(block, kinet, thermo_x)
74
+
75
+ current_time += dt
76
+ count += 1
@@ -0,0 +1,88 @@
1
+ # Saturn Reference Atmosphere Model
2
+ #
3
+ # Solar abundances relative to H2, enrichments
4
+ #
5
+ # X_He = 0.195, 0.6955
6
+ # X_CH4 = 5.50e-04, 9.4
7
+ # X_H2O = 1.026e-03, 10.0
8
+ # X_NH3 = 1.352e-04, 3.0
9
+ # X_H2S = 3.10e-05, 3.0
10
+ #
11
+ # Converted to mole fractions
12
+ #
13
+ # X_H2O = 8.91e-03
14
+ # X_NH3 = 3.52e-04
15
+ # X_H2S = 8.08e-05
16
+ #
17
+ # Dry air composition
18
+ #
19
+ # 0.86838 * H2 + 0.11778 * He + 4.49e-03 * CH4
20
+ # {H: 1.755, He: 0.118, C: 4.49e-03}
21
+
22
+ reference-state:
23
+ Tref: 0.
24
+ Pref: 1.e5
25
+
26
+ species:
27
+ - name: dry
28
+ composition: {H: 1.755, He: 0.118, C: 4.49e-03}
29
+ cv_R: 2.5
30
+
31
+ - name: H2O
32
+ composition: {H: 2, O: 1}
33
+ cv_R: 2.5
34
+ u0_R: 0.
35
+
36
+ - name: NH3
37
+ composition: {H: 2, O: 1}
38
+ cv_R: 2.5
39
+ u0_R: 0.
40
+
41
+ - name: H2S
42
+ composition: {H: 2, S: 1}
43
+ cv_R: 2.5
44
+ u0_R: 0.
45
+
46
+ - name: H2O(l)
47
+ composition: {H: 2, O: 1}
48
+ cv_R: 9.0
49
+ u0_R: -3430.
50
+
51
+ - name: NH3(s)
52
+ composition: {H: 2, O: 1}
53
+ cv_R: 9.0
54
+ u0_R: -5520.
55
+
56
+ - name: NH4SH(s)
57
+ composition: {N: 1, H: 5, S: 1}
58
+ cv_R: 9.0
59
+ u0_R: -1.2e4
60
+
61
+ geometry:
62
+ type: cartesian
63
+ bounds: {x1min: 0., x1max: 600.e3, x2min: -0.5, x2max: 0.5, x3min: -0.5, x3max: 0.5}
64
+ cells: {nx1: 200, nx2: 1, nx3: 1, nghost: 0}
65
+
66
+ dynamics:
67
+ equation-of-state:
68
+ type: moist-mixture
69
+ max-iter: 20
70
+ ftol: 1.e-6
71
+
72
+ reactions:
73
+ - equation: H2O => H2O(l)
74
+ type: nucleation
75
+ rate-constant: {formula: h2o_ideal}
76
+
77
+ - equation: NH3 => NH3(s)
78
+ type: nucleation
79
+ rate-constant: {formula: nh3_ideal}
80
+
81
+ - equation: NH3 + H2S <=> NH4SH(s)
82
+ type: nucleation
83
+ rate-constant: {formula: nh3_h2s_lewis}
84
+
85
+ boundary-condition:
86
+ external:
87
+ x1-inner: reflecting
88
+ x1-outer: reflecting
@@ -0,0 +1,40 @@
1
+ import torch
2
+ import snapy
3
+ import kintera
4
+
5
+ def evolve_kinetics(
6
+ hydro_w: torch.Tensor,
7
+ block: snapy.MeshBlock,
8
+ kinet: kintera.Kinetics,
9
+ thermo_x: kintera.ThermoX) -> torch.Tensor:
10
+ """
11
+ Evolve the chemical kinetics for one time step using implicit method.
12
+
13
+ Args:
14
+ hydro_w (torch.Tensor): The primitive variables tensor.
15
+ block (snapy.MeshBlock): The mesh block containing the simulation data.
16
+ kinet (kintera.Kinetics): The kinetics module for chemical reactions.
17
+ thermo_x (kintera.ThermoX): The thermodynamics module for computing properties.
18
+
19
+ Returns:
20
+ torch.Tensor: The change in mass density due to chemical reactions.
21
+ """
22
+ eos = block.module("hydro.eos")
23
+ thermo_y = eos.named_modules()["thermo"]
24
+
25
+ temp = eos.compute("W->T", (w,))
26
+ pres = w[index.ipr]
27
+ xfrac = thermo_y.compute("Y->X", (w[ICY:],))
28
+ conc = thermo_x.compute("TPX->V", (temp, pres, xfrac))
29
+ cp_vol = thermo_x.compute("TV->cp", (temp, conc))
30
+
31
+ conc_kinet = kinet.options.narrow_copy(conc, thermo_y.options)
32
+ rate, rc_ddC, rc_ddT = kinet.forward(temp, pres, conc_kinet)
33
+ jac = kinet.jacobian(temp, conc_kinet, cp_vol, rate, rc_ddC, rc_ddT)
34
+
35
+ stoich = kinet.buffer("stoich")
36
+ del_conc = kintera.evolve_implicit(rate, stoich, jac, dt)
37
+
38
+ inv_mu = thermo_y.buffer("inv_mu")
39
+ del_rho = del_conc / inv_mu[1:].view((1, 1, 1, -1))
40
+ return del_rho.permute((3, 0, 1, 2))
@@ -0,0 +1,72 @@
1
+ from scipy.interpolate import interp1d
2
+ import snapy
3
+ import numpy as np
4
+
5
+ from .setup_profile import setup_profile
6
+
7
+ def find_init_params(
8
+ block: snapy.MeshBlock,
9
+ param: dict[str, float],
10
+ *,
11
+ target_T: float=300.,
12
+ target_P: float=1.e5,
13
+ method: str="moist-adiabat",
14
+ max_iter: int=50,
15
+ ftol: float=1.e-2,
16
+ verbose: bool=True):
17
+ """Find initial parameters that yield desired T and P
18
+
19
+ Args:
20
+ block (snapy.MeshBlock): The mesh block to set up.
21
+ param (dict[str, float]): Initial guess parameters for the adiabat setup.
22
+ Required keys: Ts, Ps, x<species>, grav.
23
+ target_T (float, optional): Target temperature in Kelvin. Defaults to 300 K.
24
+ target_P (float, optional): Target pressure in Pascals. Defaults to 1e5 Pa.
25
+ method (str, optional): Method for the adiabat setup.
26
+ max_iter (int, optional): Maximum number of iterations. Defaults to 50.
27
+ ftol (float, optional): Tolerance for temperature convergence. Defaults to 1e-2 K.
28
+ verbose (bool, optional): If True, print iteration details. Defaults to True.
29
+
30
+ Returns:
31
+ dict: Dictionary containing the found parameters: Ts, Ps, xH2O, xNH3, xH2S.
32
+ """
33
+ count = 0
34
+ eos = block.hydro.get_eos()
35
+
36
+ while count < max_iter:
37
+ if verbose:
38
+ print(f"Iteration {count+1}: Trying Ts={param['Ts']}\n")
39
+
40
+ # setup profile
41
+ w = setup_profile(block, param, method=method)
42
+
43
+ # calculate temperature
44
+ temp = eos.compute("W->T", (w,)).squeeze()
45
+
46
+ # calculate 1D pressure
47
+ pres = w[snapy.index.ipr,...].squeeze()
48
+
49
+ # temperature function
50
+ t_func = interp1d(
51
+ pres.log().cpu().numpy(),
52
+ temp.log().cpu().numpy(),
53
+ kind="linear",
54
+ fill_value="extrapolate")
55
+
56
+ temp1 = np.exp(t_func(np.log(target_P)))
57
+ if verbose:
58
+ print(f" At P={target_P:.3e} Pa, T={temp1:.3f} K (target {target_T} K)\n")
59
+ if abs(temp1 - target_T) < ftol:
60
+ if verbose:
61
+ print("Converged! Found parameters:")
62
+ for key, val in param.items():
63
+ print(f" {key} = {val}")
64
+ print(f"Matching T = {target_T} K at P = {target_P} Pa")
65
+ return param
66
+
67
+ # adjust Ts using a damped scaling
68
+ param["Ts"] += (target_T - temp1) * 0.9
69
+ count += 1
70
+
71
+ raise RuntimeError("Failed to converge within the maximum number of iterations.")
72
+
@@ -0,0 +1,283 @@
1
+ from typing import Tuple
2
+
3
+ import torch
4
+ import snapy
5
+ import kintera
6
+
7
+ def integrate_neutral(
8
+ thermo_x: kintera.ThermoX,
9
+ temp: torch.Tensor,
10
+ pres: torch.Tensor,
11
+ xfrac: torch.Tensor,
12
+ grav: float,
13
+ dz: float,
14
+ max_iter: int = 100
15
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
16
+ """
17
+ A neutral density profile assumes no cloud and:
18
+
19
+ (1) dP/dz = -rho*g
20
+ (2) d(rho)/dz = ...
21
+
22
+ In discretized form:
23
+
24
+ rho_bar = 0.5 * (rho_old + rho_ad)
25
+ P_new = P_old - rho_bar * g * dz
26
+ """
27
+ conc = thermo_x.compute("TPX->V", [temp, pres, xfrac])
28
+ rho = thermo_x.compute("V->D", [conc])
29
+
30
+ # make an adiabatic step first
31
+ temp_ad = temp.clone()
32
+ pres_ad = pres.clone()
33
+ xfrac_ad = xfrac.clone()
34
+
35
+ thermo_x.extrapolate_ad(temp_ad, pres_ad, xfrac_ad, grav, dz)
36
+ conc_ad = thermo_x.compute("TPX->V", [temp_ad, pres_ad, xfrac_ad])
37
+ rho_ad = thermo_x.compute("V->D", [conc_ad])
38
+ rho_bar = 0.5 * (rho + rho_ad)
39
+
40
+ pres2 = pres - rho_bar * grav * dz
41
+
42
+ # initial guess
43
+ temp2 = temp_ad.clone()
44
+ count = 0
45
+ while count < max_iter:
46
+ xfrac2 = xfrac_ad.clone()
47
+
48
+ # equilibrate clouds
49
+ thermo_x.forward(temp2, pres2, xfrac2)
50
+
51
+ # drop clouds fractions
52
+ for cid in thermo_x.options.cloud_ids():
53
+ xfrac2[..., cid] = 0.0
54
+ # renormalize mole fractions
55
+ xfrac2 /= xfrac.sum(dim=-1, keepdim=True)
56
+
57
+ conc2 = thermo_x.compute("TPX->V", [temp2, pres2, xfrac2])
58
+ rho2 = thermo_x.compute("V->D", [conc2])
59
+
60
+ if torch.allclose(rho2, rho_ad):
61
+ break
62
+
63
+ temp2 -= temp2 * (rho_ad - rho2) / rho2
64
+ count += 1
65
+
66
+ if count == max_iter:
67
+ raise RuntimeError("neutral density integration did not converge.")
68
+
69
+ return temp2, pres2, xfrac2
70
+
71
+ def integrate_dry_adiabat(
72
+ thermo_x: kintera.ThermoX,
73
+ temp: torch.Tensor,
74
+ pres: torch.Tensor,
75
+ xfrac: torch.Tensor,
76
+ grav: float,
77
+ dz: float,
78
+ max_iter: int = 100
79
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
80
+ """
81
+ A dry adiabatic profile assumes no cloud and:
82
+
83
+ (1) dT/dz = -g/cp
84
+ (2) dP/dz = -rho*g
85
+
86
+ In discretized form:
87
+
88
+ cp_bar = 0.5 * (cp(T_old) + cp(T_new))
89
+ T_new = T_old - g/bar * dz
90
+ rho_bar = 0.5 * (rho_old + rho_new)
91
+ P_new = P_old - rho_bar * g * dz
92
+
93
+ """
94
+ conc1 = thermo_x.compute("TPX->V", [temp, pres, xfrac])
95
+ cp1 = thermo_x.compute("TV->cp", [temp, conc1]) / conc1.sum(-1)
96
+ rho1 = thermo_x.compute("V->D", [conc1])
97
+ mmw1 = (thermo_x.mu * xfrac).sum(-1)
98
+
99
+ # initial guess
100
+ temp2 = temp - grav * mmw1 * dz / cp1
101
+ pres2 = pres - rho1 * grav * dz
102
+
103
+ count = 0
104
+ while count < max_iter:
105
+ xfrac2 = xfrac.clone()
106
+
107
+ # equilibrate clouds
108
+ thermo_x.forward(temp2, pres2, xfrac2)
109
+
110
+ # drop clouds fractions
111
+ for cid in thermo_x.options.cloud_ids():
112
+ xfrac2[..., cid] = 0.0
113
+ # renormalize mole fractions
114
+ xfrac2 /= xfrac.sum(dim=-1, keepdim=True)
115
+
116
+ conc2 = thermo_x.compute("TPX->V", [temp2, pres2, xfrac2])
117
+ cp2 = thermo_x.compute("TV->cp", [temp2, conc2]) / conc2.sum(-1)
118
+ rho2 = thermo_x.compute("V->D", [conc2])
119
+ mmw2 = (thermo_x.mu * xfrac2).sum(-1)
120
+
121
+ cp_bar = 0.5 * (cp1 / mmw1 + cp2 / mmw2)
122
+ rho_bar = 0.5 * (rho1 + rho2)
123
+
124
+ temp_new = temp - grav * dz / cp_bar
125
+ pres_new = pres - rho_bar * grav * dz
126
+
127
+ if torch.allclose(temp_new, temp2) and torch.allclose(pres_new, pres2):
128
+ break
129
+
130
+ temp2 = temp_new
131
+ pres2 = pres_new
132
+ count += 1
133
+
134
+ if count == max_iter:
135
+ raise RuntimeError("Dry adiabat integration did not converge.")
136
+
137
+ return temp2, pres2, xfrac2
138
+
139
+ def setup_profile(
140
+ block: snapy.MeshBlock,
141
+ param: dict[str, float] = {},
142
+ method: str = "moist-adiabat"
143
+ ) -> torch.Tensor:
144
+ """
145
+ Set up an adiabatic initial condition for the mesh block.
146
+
147
+ This function initializes the primitive variables in the mesh block
148
+ and returns the initialized tensor.
149
+
150
+ Args:
151
+ block (snapy.MeshBlock): The mesh block to set up.
152
+ param (dict[str, float], optional): Parameters for the adiabat setup. Defaults to {}.
153
+ method (str, optional): Method for the adiabat setup. Choose between
154
+ (1) "dry-adiabat"
155
+ (2) "moist-adiabat"
156
+ (3) "isothermal"
157
+ (4) "pseudo-adiabat"
158
+ (5) "neutral"
159
+ Defaults to "moist-adiabat".
160
+
161
+ Required parameters in `param`:
162
+ Ts (float): Surface temperature in Kelvin. Default is 300 K.
163
+ Ps (float): Surface pressure in Pascals. Default is 1e5 Pa.
164
+ x<species> (float): Mole fraction of a specific species (e.g., xH2O for
165
+ water vapor). Default is 0.0.
166
+ grav (float): Gravitational acceleration in m/s^2. Default is 9.8 m/s^2.
167
+
168
+ Returns:
169
+ torch.Tensor: The initialized primitive variables tensor.
170
+ """
171
+
172
+ # check method
173
+ valid_methods = [
174
+ "dry-adiabat",
175
+ "moist-adiabat",
176
+ "isothermal",
177
+ "pseudo-adiabat",
178
+ "neutral"
179
+ ]
180
+
181
+ if method not in valid_methods:
182
+ raise ValueError(f"Invalid method '{method}'. Choose from {valid_methods}.")
183
+
184
+ Ts = param.get("Ts", 300.)
185
+ Ps = param.get("Ps", 1.e5)
186
+ grav = param.get("grav", 9.8)
187
+ Tmin = param.get("Tmin", 0.)
188
+
189
+ # get handles to modules
190
+ coord = block.module("hydro.coord")
191
+ thermo_y = block.module("hydro.eos.thermo")
192
+
193
+ # get coordinates
194
+ x3v, x2v, x1v = torch.meshgrid(
195
+ coord.buffer("x3v"), coord.buffer("x2v"), coord.buffer("x1v"), indexing="ij"
196
+ )
197
+
198
+ # handling mole fractions
199
+ thermo_x = kintera.ThermoX(thermo_y.options)
200
+ thermo_x.to(dtype=x1v.dtype, device=x1v.device)
201
+
202
+ # get dimensions
203
+ nc3, nc2, nc1 = x1v.shape
204
+ ny = len(thermo_y.options.species()) - 1
205
+ nvar = 5 + ny
206
+
207
+ w = torch.zeros((nvar, nc3, nc2, nc1),
208
+ dtype=x1v.dtype, device=x1v.device)
209
+
210
+ temp = Ts * torch.ones((nc3, nc2), dtype=w.dtype, device=w.device)
211
+ pres = Ps * torch.ones((nc3, nc2), dtype=w.dtype, device=w.device)
212
+ xfrac = torch.zeros((nc3, nc2, ny + 1), dtype=w.dtype, device=w.device)
213
+
214
+ for name in thermo_y.options.species():
215
+ index = thermo_y.options.species().index(name)
216
+ xfrac[..., index] = param.get(f"x{name}", 0.0)
217
+
218
+ # dry air mole fraction
219
+ xfrac[..., 0] = 1. - xfrac[..., 1:].sum(dim=-1)
220
+
221
+ # start and end indices for the vertical direction
222
+ # excluding ghost cells
223
+ ifirst = coord.ifirst()
224
+ ilast = coord.ilast()
225
+
226
+ # vertical grid distance of the first cell
227
+ dz = coord.buffer("dx1f")[ifirst]
228
+
229
+ # half a grid to cell center
230
+ thermo_x.extrapolate_ad(temp, pres, xfrac, grav, dz / 2.);
231
+
232
+ # adiabatic extrapolation
233
+ if method == "isothermal":
234
+ i_isothermal = ifirst
235
+ ifirst = ilast
236
+ else:
237
+ i_isothermal = ilast
238
+
239
+ for i in range(ifirst, ilast):
240
+ # drop clouds fractions
241
+ if method.split("-")[0] != "moist":
242
+ for cid in thermo_x.options.cloud_ids():
243
+ xfrac[..., cid] = 0.0
244
+ # renormalize mole fractions
245
+ xfrac /= xfrac.sum(dim=-1, keepdim=True)
246
+ conc = thermo_x.compute("TPX->V", [temp, pres, xfrac])
247
+
248
+ w[snapy.index.ipr, ..., i] = pres;
249
+ w[snapy.index.idn, ..., i] = thermo_x.compute("V->D", [conc])
250
+ w[snapy.index.icy:, ...,i] = thermo_x.compute("X->Y", [xfrac])
251
+
252
+ dz = coord.buffer("dx1f")[i]
253
+ if method.split("-")[0] == "dry":
254
+ temp, pres, xfrac = integrate_dry_adiabat(thermo_x, temp, pres, xfrac, grav, dz);
255
+ elif method.split("-")[0] == "neutral":
256
+ temp, pres, xfrac = integrate_neutral(thermo_x, temp, pres, xfrac, grav, dz);
257
+ else:
258
+ thermo_x.extrapolate_ad(temp, pres, xfrac, grav, dz);
259
+
260
+ if torch.any(temp < Tmin):
261
+ i_isothermal = i + 1
262
+ break
263
+
264
+ # isothermal extrapolation
265
+ for i in range(i_isothermal, ilast):
266
+ # drop clouds fractions
267
+ if method.split("-")[0] != "moist":
268
+ for cid in thermo_x.options.cloud_ids():
269
+ xfrac[..., cid] = 0.0
270
+ # renormalize mole fractions
271
+ xfrac /= xfrac.sum(dim=-1, keepdim=True)
272
+
273
+ mu = (thermo_x.mu * xfrac).sum(-1)
274
+ dz = coord.buffer("dx1f")[i]
275
+ pres *= torch.exp(-grav * mu * dz / (kintera.constants.Rgas * temp))
276
+ conc = thermo_x.compute("TPX->V", [temp, pres, xfrac])
277
+ w[snapy.index.ipr, ..., i] = pres
278
+ w[snapy.index.idn, ..., i] = thermo_x.compute("V->D", [conc])
279
+ w[snapy.index.icy:, ..., i] = thermo_x.compute("X->Y", [xfrac])
280
+
281
+ # initialize hydro state
282
+ block.initialize({"hydro_w": w})
283
+ return w