Sompote commited on
Commit
519a0b4
·
verified ·
1 Parent(s): b50196d

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +31 -24
  2. pvd_consolidation.py +132 -29
app.py CHANGED
@@ -44,17 +44,23 @@ for i in range(n_layers):
44
  RR = st.number_input(
45
  f"RR", min_value=0.001, value=0.05, step=0.01, format="%.3f", key=f"rr_{i}"
46
  )
47
-
48
- with col2:
49
  CR = st.number_input(
50
  f"CR", min_value=0.001, value=0.30, step=0.01, format="%.3f", key=f"cr_{i}"
51
  )
 
 
52
  sigma_ini = st.number_input(
53
  f"σ'ini (kPa)", min_value=1.0, value=50.0, step=5.0, key=f"sini_{i}"
54
  )
55
  sigma_p = st.number_input(
56
  f"σ'p (kPa)", min_value=1.0, value=80.0, step=5.0, key=f"sp_{i}"
57
  )
 
 
 
 
 
 
58
 
59
  layers.append(
60
  SoilLayer(
@@ -65,6 +71,8 @@ for i in range(n_layers):
65
  CR=CR,
66
  sigma_ini=sigma_ini,
67
  sigma_p=sigma_p,
 
 
68
  )
69
  )
70
 
@@ -72,32 +80,28 @@ for i in range(n_layers):
72
 
73
  # PVD Properties
74
  st.sidebar.subheader("PVD Properties")
75
- col1, col2 = st.sidebar.columns(2)
76
-
77
- with col1:
78
- dw = st.number_input(
79
- "Drain diameter dw (m)", min_value=0.01, value=0.05, step=0.01, format="%.3f"
80
- )
81
- ds = st.number_input(
82
- "Smear zone ds (m)", min_value=0.01, value=0.15, step=0.01, format="%.3f"
83
- )
84
- De = st.number_input("Unit cell De (m)", min_value=0.1, value=1.5, step=0.1)
85
- L_drain = st.number_input("Drain length L (m)", min_value=1.0, value=10.0, step=1.0)
86
 
87
- with col2:
88
- kh = st.number_input("kh (m/yr)", min_value=0.1, value=2.0, step=0.1)
89
- ks = st.number_input("ks (m/yr)", min_value=0.1, value=1.0, step=0.1)
 
 
 
 
 
 
 
90
 
91
- well_resistance = st.selectbox(
92
- "Well Resistance", ["Negligible (qw → ∞)", "Custom qw"]
93
- )
94
 
95
- if well_resistance == "Custom qw":
96
- qw = st.number_input("qw (m³/yr)", min_value=1.0, value=100.0, step=10.0)
97
- else:
98
- qw = 1e12 # Very large value for negligible resistance
99
 
100
- pvd = PVDProperties(dw=dw, ds=ds, De=De, L_drain=L_drain, kh=kh, ks=ks, qw=qw)
101
 
102
  # Analysis Parameters
103
  st.sidebar.subheader("Analysis Parameters")
@@ -152,6 +156,9 @@ if hasattr(st.session_state, "analysis_done") and st.session_state.analysis_done
152
  ax.grid(True, alpha=0.3, linestyle="--")
153
  ax.tick_params(labelsize=12)
154
 
 
 
 
155
  # Add final settlement annotation
156
  final_settlement = st.session_state.settlement[-1]
157
  ax.axhline(
 
44
  RR = st.number_input(
45
  f"RR", min_value=0.001, value=0.05, step=0.01, format="%.3f", key=f"rr_{i}"
46
  )
 
 
47
  CR = st.number_input(
48
  f"CR", min_value=0.001, value=0.30, step=0.01, format="%.3f", key=f"cr_{i}"
49
  )
50
+
51
+ with col2:
52
  sigma_ini = st.number_input(
53
  f"σ'ini (kPa)", min_value=1.0, value=50.0, step=5.0, key=f"sini_{i}"
54
  )
55
  sigma_p = st.number_input(
56
  f"σ'p (kPa)", min_value=1.0, value=80.0, step=5.0, key=f"sp_{i}"
57
  )
58
+ kh = st.number_input(
59
+ f"kh (m/yr)", min_value=0.1, value=2.0, step=0.1, key=f"kh_{i}"
60
+ )
61
+ ks = st.number_input(
62
+ f"ks (m/yr)", min_value=0.1, value=1.0, step=0.1, key=f"ks_{i}"
63
+ )
64
 
65
  layers.append(
66
  SoilLayer(
 
71
  CR=CR,
72
  sigma_ini=sigma_ini,
73
  sigma_p=sigma_p,
74
+ kh=kh,
75
+ ks=ks,
76
  )
77
  )
78
 
 
80
 
81
  # PVD Properties
82
  st.sidebar.subheader("PVD Properties")
 
 
 
 
 
 
 
 
 
 
 
83
 
84
+ dw = st.sidebar.number_input(
85
+ "Drain diameter dw (m)", min_value=0.01, value=0.05, step=0.01, format="%.3f"
86
+ )
87
+ ds = st.sidebar.number_input(
88
+ "Smear zone ds (m)", min_value=0.01, value=0.15, step=0.01, format="%.3f"
89
+ )
90
+ De = st.sidebar.number_input("Unit cell De (m)", min_value=0.1, value=1.5, step=0.1)
91
+ L_drain = st.sidebar.number_input(
92
+ "Drain length L (m)", min_value=1.0, value=10.0, step=1.0
93
+ )
94
 
95
+ well_resistance = st.sidebar.selectbox(
96
+ "Well Resistance", ["Negligible (qw → ∞)", "Custom qw"]
97
+ )
98
 
99
+ if well_resistance == "Custom qw":
100
+ qw = st.sidebar.number_input("qw (m³/yr)", min_value=1.0, value=100.0, step=10.0)
101
+ else:
102
+ qw = 1e12 # Very large value for negligible resistance
103
 
104
+ pvd = PVDProperties(dw=dw, ds=ds, De=De, L_drain=L_drain, qw=qw)
105
 
106
  # Analysis Parameters
107
  st.sidebar.subheader("Analysis Parameters")
 
156
  ax.grid(True, alpha=0.3, linestyle="--")
157
  ax.tick_params(labelsize=12)
158
 
159
+ # Invert y-axis (settlement goes down)
160
+ ax.invert_yaxis()
161
+
162
  # Add final settlement annotation
163
  final_settlement = st.session_state.settlement[-1]
164
  ax.axhline(
pvd_consolidation.py CHANGED
@@ -23,6 +23,8 @@ class SoilLayer:
23
  CR: float # Compression ratio
24
  sigma_ini: float # Initial effective stress (kPa)
25
  sigma_p: float # Preconsolidation pressure (kPa)
 
 
26
 
27
 
28
  @dataclass
@@ -33,8 +35,6 @@ class PVDProperties:
33
  ds: float # Smear zone diameter (m)
34
  De: float # Equivalent diameter of unit cell (m)
35
  L_drain: float # Total drain spacing for two-way drainage (m)
36
- kh: float # Horizontal permeability (m/year)
37
- ks: float # Smear zone permeability (m/year)
38
  qw: float # Well discharge capacity (m³/year)
39
 
40
 
@@ -84,6 +84,8 @@ class PVDConsolidation:
84
  self.layer_indices = []
85
  self.Cv_profile = []
86
  self.Ch_profile = []
 
 
87
 
88
  z = 0
89
  for i, layer in enumerate(self.layers):
@@ -94,6 +96,8 @@ class PVDConsolidation:
94
  self.layer_indices.extend([i] * len(z_layer))
95
  self.Cv_profile.extend([layer.Cv] * len(z_layer))
96
  self.Ch_profile.extend([layer.Ch] * len(z_layer))
 
 
97
 
98
  z += layer.thickness
99
 
@@ -102,24 +106,65 @@ class PVDConsolidation:
102
  self.layer_indices.append(len(self.layers) - 1)
103
  self.Cv_profile.append(self.layers[-1].Cv)
104
  self.Ch_profile.append(self.layers[-1].Ch)
 
 
105
 
106
  self.z_coords = np.array(self.z_coords)
107
  self.layer_indices = np.array(self.layer_indices)
108
  self.Cv_profile = np.array(self.Cv_profile)
109
  self.Ch_profile = np.array(self.Ch_profile)
 
 
110
 
111
  self.n_nodes = len(self.z_coords)
112
  self.dz = np.diff(self.z_coords)
113
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  def calculate_pvd_factors(self) -> Tuple[float, float, float]:
115
  """
116
- Calculate PVD influence factors
117
 
118
  Returns:
119
  --------
120
  Fn, Fs, Fr : float
121
  Geometric, smear, and well resistance factors
122
  """
 
 
 
 
123
  # Geometric factor (n ratio)
124
  n = self.pvd.De / self.pvd.dw
125
 
@@ -128,7 +173,7 @@ class PVDConsolidation:
128
 
129
  # Fs - Smear effect factor
130
  s = self.pvd.ds / self.pvd.dw
131
- Fs = ((self.pvd.kh / self.pvd.ks) - 1) * np.log(s)
132
 
133
  # Fr - Well resistance factor
134
  # For typical band drains: Fr = π(2L-l)l/(qw·kh) where l = L/2
@@ -139,14 +184,15 @@ class PVDConsolidation:
139
  else:
140
  # Using Hansbo formula: Fr = (L²/(8·qw))·(kh)
141
  # More typical: Fr = π·L²·kh/(8·qw) for two-way drainage
142
- Fr = (np.pi * L**2 * self.pvd.kh) / (8 * self.pvd.qw)
143
 
144
  return Fn, Fs, Fr
145
 
146
  def calculate_Uh(self, t: float) -> np.ndarray:
147
  """
148
  Calculate degree of consolidation in horizontal direction
149
- using finite difference method
 
150
 
151
  Parameters:
152
  -----------
@@ -157,10 +203,13 @@ class PVDConsolidation:
157
  --------
158
  Uh : ndarray
159
  Degree of horizontal consolidation at each node
 
160
  """
161
- # Calculate PVD factors
162
- Fn, Fs, Fr = self.calculate_pvd_factors()
163
- F_total = Fn + Fs + Fr
 
 
164
 
165
  # Initialize excess pore pressure (normalized)
166
  u = np.ones(self.n_nodes) # Initially all excess pore pressure = surcharge
@@ -177,15 +226,26 @@ class PVDConsolidation:
177
  # Simplified for radial drainage with PVD
178
 
179
  for i in range(1, self.n_nodes - 1):
180
- Ch = self.Ch_profile[i]
181
-
182
- # Radial drainage term (simplified)
183
- # Using equivalent time factor approach
184
- Th = Ch * self.dt / (self.pvd.De**2 / 4)
185
-
186
- # Update excess pore pressure
187
- decay_rate = 8 * Th / F_total
188
- u_new[i] = u[i] * np.exp(-decay_rate)
 
 
 
 
 
 
 
 
 
 
 
189
 
190
  # Boundary conditions
191
  u_new[0] = 0 # Top drainage
@@ -239,7 +299,8 @@ class PVDConsolidation:
239
  """
240
  Calculate total degree of consolidation (combined vertical and horizontal)
241
 
242
- U = 1 - (1 - Uh)(1 - Uv)
 
243
 
244
  Parameters:
245
  -----------
@@ -254,7 +315,17 @@ class PVDConsolidation:
254
  Uh = self.calculate_Uh(t)
255
  Uv = self.calculate_Uv(t)
256
 
257
- U = 1 - (1 - Uh) * (1 - Uv)
 
 
 
 
 
 
 
 
 
 
258
 
259
  return U
260
 
@@ -410,6 +481,17 @@ class PVDConsolidation:
410
  ax1.plot(Uh * 100, self.z_coords, "r-", linewidth=2, label="Horizontal (Uh)")
411
  ax1.plot(Uv * 100, self.z_coords, "b-", linewidth=2, label="Vertical (Uv)")
412
  ax1.plot(U * 100, self.z_coords, "g-", linewidth=2, label="Total (U)")
 
 
 
 
 
 
 
 
 
 
 
413
  ax1.set_xlabel("Degree of Consolidation (%)", fontsize=12)
414
  ax1.set_ylabel("Depth (m)", fontsize=12)
415
  ax1.set_title(
@@ -463,25 +545,42 @@ class PVDConsolidation:
463
  report += f" Equivalent drain diameter (dw): {self.pvd.dw:.3f} m\n"
464
  report += f" Smear zone diameter (ds): {self.pvd.ds:.3f} m\n"
465
  report += f" Unit cell diameter (De): {self.pvd.De:.3f} m\n"
 
466
  report += (
467
  f" Drain spacing ratio (n = De/dw): {self.pvd.De / self.pvd.dw:.2f}\n"
468
  )
469
  report += f" Geometric factor (Fn): {Fn:.4f}\n"
470
- report += f" Smear factor (Fs): {Fs:.4f}\n"
471
- report += f" Well resistance (Fr): {Fr:.4f}\n"
472
- report += f" Total resistance (F): {Fn + Fs + Fr:.4f}\n\n"
473
 
474
  report += "SOIL PROFILE:\n"
 
475
  for i, layer in enumerate(self.layers):
476
- report += f" Layer {i + 1}:\n"
 
 
 
 
 
 
 
 
 
 
 
477
  report += f" Thickness: {layer.thickness:.2f} m\n"
478
  report += f" Ch: {layer.Ch:.4f} m²/year\n"
479
  report += f" Cv: {layer.Cv:.4f} m²/year\n"
 
 
480
  report += f" RR: {layer.RR:.4f}\n"
481
  report += f" CR: {layer.CR:.4f}\n"
482
  report += f" σ'ini: {layer.sigma_ini:.1f} kPa\n"
483
  report += f" σ'p: {layer.sigma_p:.1f} kPa\n\n"
484
 
 
 
485
  report += f"Applied surcharge: {self.surcharge:.1f} kPa\n\n"
486
  report += "=" * 70 + "\n"
487
  report += "SETTLEMENT vs TIME:\n"
@@ -513,6 +612,8 @@ def example_usage():
513
  CR=0.30, # Compression ratio
514
  sigma_ini=50.0, # Initial effective stress 50 kPa
515
  sigma_p=80.0, # Preconsolidation pressure 80 kPa
 
 
516
  ),
517
  SoilLayer(
518
  thickness=8.0, # 8 m thick
@@ -522,6 +623,8 @@ def example_usage():
522
  CR=0.35,
523
  sigma_ini=90.0,
524
  sigma_p=90.0,
 
 
525
  ),
526
  SoilLayer(
527
  thickness=7.0, # 7 m thick
@@ -531,6 +634,8 @@ def example_usage():
531
  CR=0.32,
532
  sigma_ini=140.0,
533
  sigma_p=150.0,
 
 
534
  ),
535
  ]
536
 
@@ -540,9 +645,7 @@ def example_usage():
540
  ds=0.15, # 150 mm smear zone diameter
541
  De=1.5, # 1.5 m equivalent unit cell diameter (triangular spacing)
542
  L_drain=20.0, # 20 m total drain length (two-way drainage)
543
- kh=2.0, # 2 m/year horizontal permeability
544
- ks=0.5, # 0.5 m/year smear zone permeability
545
- qw=50.0, # 50 m³/year well discharge capacity
546
  )
547
 
548
  # Applied surcharge
@@ -617,6 +720,8 @@ def create_analysis_from_yaml(yaml_file: str) -> PVDConsolidation:
617
  CR=layer_data["CR"],
618
  sigma_ini=layer_data["sigma_ini"],
619
  sigma_p=layer_data["sigma_p"],
 
 
620
  )
621
  layers.append(layer)
622
 
@@ -627,8 +732,6 @@ def create_analysis_from_yaml(yaml_file: str) -> PVDConsolidation:
627
  ds=pvd_data["ds"],
628
  De=pvd_data["De"],
629
  L_drain=pvd_data["L_drain"],
630
- kh=pvd_data["kh"],
631
- ks=pvd_data["ks"],
632
  qw=pvd_data["qw"],
633
  )
634
 
 
23
  CR: float # Compression ratio
24
  sigma_ini: float # Initial effective stress (kPa)
25
  sigma_p: float # Preconsolidation pressure (kPa)
26
+ kh: float # Horizontal permeability (m/year)
27
+ ks: float # Smear zone permeability (m/year)
28
 
29
 
30
  @dataclass
 
35
  ds: float # Smear zone diameter (m)
36
  De: float # Equivalent diameter of unit cell (m)
37
  L_drain: float # Total drain spacing for two-way drainage (m)
 
 
38
  qw: float # Well discharge capacity (m³/year)
39
 
40
 
 
84
  self.layer_indices = []
85
  self.Cv_profile = []
86
  self.Ch_profile = []
87
+ self.kh_profile = []
88
+ self.ks_profile = []
89
 
90
  z = 0
91
  for i, layer in enumerate(self.layers):
 
96
  self.layer_indices.extend([i] * len(z_layer))
97
  self.Cv_profile.extend([layer.Cv] * len(z_layer))
98
  self.Ch_profile.extend([layer.Ch] * len(z_layer))
99
+ self.kh_profile.extend([layer.kh] * len(z_layer))
100
+ self.ks_profile.extend([layer.ks] * len(z_layer))
101
 
102
  z += layer.thickness
103
 
 
106
  self.layer_indices.append(len(self.layers) - 1)
107
  self.Cv_profile.append(self.layers[-1].Cv)
108
  self.Ch_profile.append(self.layers[-1].Ch)
109
+ self.kh_profile.append(self.layers[-1].kh)
110
+ self.ks_profile.append(self.layers[-1].ks)
111
 
112
  self.z_coords = np.array(self.z_coords)
113
  self.layer_indices = np.array(self.layer_indices)
114
  self.Cv_profile = np.array(self.Cv_profile)
115
  self.Ch_profile = np.array(self.Ch_profile)
116
+ self.kh_profile = np.array(self.kh_profile)
117
+ self.ks_profile = np.array(self.ks_profile)
118
 
119
  self.n_nodes = len(self.z_coords)
120
  self.dz = np.diff(self.z_coords)
121
 
122
+ def calculate_pvd_factors_layer(self, layer_idx: int) -> Tuple[float, float, float]:
123
+ """
124
+ Calculate PVD influence factors for a specific layer
125
+
126
+ Parameters:
127
+ -----------
128
+ layer_idx : int
129
+ Layer index
130
+
131
+ Returns:
132
+ --------
133
+ Fn, Fs, Fr : float
134
+ Geometric, smear, and well resistance factors for the layer
135
+ """
136
+ layer = self.layers[layer_idx]
137
+
138
+ # Geometric factor (n ratio) - same for all layers
139
+ n = self.pvd.De / self.pvd.dw
140
+ Fn = (n**2 / (n**2 - 1)) * np.log(n) - 3 / 4 + 1 / n**2
141
+
142
+ # Fs - Smear effect factor (layer-specific)
143
+ s = self.pvd.ds / self.pvd.dw
144
+ Fs = ((layer.kh / layer.ks) - 1) * np.log(s)
145
+
146
+ # Fr - Well resistance factor (layer-specific)
147
+ L = self.pvd.L_drain
148
+ if self.pvd.qw > 1e10: # If qw is very large, assume negligible well resistance
149
+ Fr = 0.0
150
+ else:
151
+ Fr = (np.pi * L**2 * layer.kh) / (8 * self.pvd.qw)
152
+
153
+ return Fn, Fs, Fr
154
+
155
  def calculate_pvd_factors(self) -> Tuple[float, float, float]:
156
  """
157
+ Calculate PVD influence factors using weighted average permeabilities
158
 
159
  Returns:
160
  --------
161
  Fn, Fs, Fr : float
162
  Geometric, smear, and well resistance factors
163
  """
164
+ # Use thickness-weighted average kh and ks
165
+ kh_avg = np.average(self.kh_profile, weights=np.ones(len(self.kh_profile)))
166
+ ks_avg = np.average(self.ks_profile, weights=np.ones(len(self.ks_profile)))
167
+
168
  # Geometric factor (n ratio)
169
  n = self.pvd.De / self.pvd.dw
170
 
 
173
 
174
  # Fs - Smear effect factor
175
  s = self.pvd.ds / self.pvd.dw
176
+ Fs = ((kh_avg / ks_avg) - 1) * np.log(s)
177
 
178
  # Fr - Well resistance factor
179
  # For typical band drains: Fr = π(2L-l)l/(qw·kh) where l = L/2
 
184
  else:
185
  # Using Hansbo formula: Fr = (L²/(8·qw))·(kh)
186
  # More typical: Fr = π·L²·kh/(8·qw) for two-way drainage
187
+ Fr = (np.pi * L**2 * kh_avg) / (8 * self.pvd.qw)
188
 
189
  return Fn, Fs, Fr
190
 
191
  def calculate_Uh(self, t: float) -> np.ndarray:
192
  """
193
  Calculate degree of consolidation in horizontal direction
194
+ using finite difference method with layer-specific PVD factors
195
+ Only applies to layers within drain length L
196
 
197
  Parameters:
198
  -----------
 
203
  --------
204
  Uh : ndarray
205
  Degree of horizontal consolidation at each node
206
+ (0 for nodes beyond drain length)
207
  """
208
+ # Pre-calculate F_total for each layer
209
+ F_total_layers = []
210
+ for layer_idx in range(len(self.layers)):
211
+ Fn, Fs, Fr = self.calculate_pvd_factors_layer(layer_idx)
212
+ F_total_layers.append(Fn + Fs + Fr)
213
 
214
  # Initialize excess pore pressure (normalized)
215
  u = np.ones(self.n_nodes) # Initially all excess pore pressure = surcharge
 
226
  # Simplified for radial drainage with PVD
227
 
228
  for i in range(1, self.n_nodes - 1):
229
+ # Check if this node is within drain length
230
+ depth = self.z_coords[i]
231
+
232
+ if depth <= self.pvd.L_drain:
233
+ # Within drain length - apply horizontal consolidation
234
+ Ch = self.Ch_profile[i]
235
+ layer_idx = self.layer_indices[i]
236
+ F_total = F_total_layers[layer_idx]
237
+
238
+ # Radial drainage term (simplified)
239
+ # Using equivalent time factor approach
240
+ Th = Ch * self.dt / (self.pvd.De**2 / 4)
241
+
242
+ # Update excess pore pressure
243
+ decay_rate = 8 * Th / F_total
244
+ u_new[i] = u[i] * np.exp(-decay_rate)
245
+ else:
246
+ # Beyond drain length - no horizontal consolidation
247
+ # u remains unchanged (Uh = 0)
248
+ pass
249
 
250
  # Boundary conditions
251
  u_new[0] = 0 # Top drainage
 
299
  """
300
  Calculate total degree of consolidation (combined vertical and horizontal)
301
 
302
+ For nodes within drain length: U = 1 - (1 - Uh)(1 - Uv)
303
+ For nodes beyond drain length: U = Uv (only vertical consolidation)
304
 
305
  Parameters:
306
  -----------
 
315
  Uh = self.calculate_Uh(t)
316
  Uv = self.calculate_Uv(t)
317
 
318
+ U = np.zeros(self.n_nodes)
319
+
320
+ for i in range(self.n_nodes):
321
+ depth = self.z_coords[i]
322
+
323
+ if depth <= self.pvd.L_drain:
324
+ # Within drain length - combined vertical and horizontal
325
+ U[i] = 1 - (1 - Uh[i]) * (1 - Uv[i])
326
+ else:
327
+ # Beyond drain length - only vertical consolidation
328
+ U[i] = Uv[i]
329
 
330
  return U
331
 
 
481
  ax1.plot(Uh * 100, self.z_coords, "r-", linewidth=2, label="Horizontal (Uh)")
482
  ax1.plot(Uv * 100, self.z_coords, "b-", linewidth=2, label="Vertical (Uv)")
483
  ax1.plot(U * 100, self.z_coords, "g-", linewidth=2, label="Total (U)")
484
+
485
+ # Add drain length line
486
+ ax1.axhline(
487
+ y=self.pvd.L_drain,
488
+ color="orange",
489
+ linestyle="--",
490
+ linewidth=1.5,
491
+ alpha=0.7,
492
+ label=f"Drain Length = {self.pvd.L_drain:.1f} m",
493
+ )
494
+
495
  ax1.set_xlabel("Degree of Consolidation (%)", fontsize=12)
496
  ax1.set_ylabel("Depth (m)", fontsize=12)
497
  ax1.set_title(
 
545
  report += f" Equivalent drain diameter (dw): {self.pvd.dw:.3f} m\n"
546
  report += f" Smear zone diameter (ds): {self.pvd.ds:.3f} m\n"
547
  report += f" Unit cell diameter (De): {self.pvd.De:.3f} m\n"
548
+ report += f" Drain length (L): {self.pvd.L_drain:.2f} m\n"
549
  report += (
550
  f" Drain spacing ratio (n = De/dw): {self.pvd.De / self.pvd.dw:.2f}\n"
551
  )
552
  report += f" Geometric factor (Fn): {Fn:.4f}\n"
553
+ report += f" Smear factor (Fs - avg): {Fs:.4f}\n"
554
+ report += f" Well resistance (Fr - avg): {Fr:.4f}\n"
555
+ report += f" Total resistance (F - avg): {Fn + Fs + Fr:.4f}\n\n"
556
 
557
  report += "SOIL PROFILE:\n"
558
+ cumulative_depth = 0
559
  for i, layer in enumerate(self.layers):
560
+ depth_top = cumulative_depth
561
+ depth_bottom = cumulative_depth + layer.thickness
562
+
563
+ # Check if layer is within drain length
564
+ if depth_bottom <= self.pvd.L_drain:
565
+ drain_status = "Full PVD effect (Uh + Uv)"
566
+ elif depth_top >= self.pvd.L_drain:
567
+ drain_status = "No PVD effect (Uv only)"
568
+ else:
569
+ drain_status = "Partial PVD effect"
570
+
571
+ report += f" Layer {i + 1} ({depth_top:.1f}m - {depth_bottom:.1f}m): {drain_status}\n"
572
  report += f" Thickness: {layer.thickness:.2f} m\n"
573
  report += f" Ch: {layer.Ch:.4f} m²/year\n"
574
  report += f" Cv: {layer.Cv:.4f} m²/year\n"
575
+ report += f" kh: {layer.kh:.4f} m/year\n"
576
+ report += f" ks: {layer.ks:.4f} m/year\n"
577
  report += f" RR: {layer.RR:.4f}\n"
578
  report += f" CR: {layer.CR:.4f}\n"
579
  report += f" σ'ini: {layer.sigma_ini:.1f} kPa\n"
580
  report += f" σ'p: {layer.sigma_p:.1f} kPa\n\n"
581
 
582
+ cumulative_depth = depth_bottom
583
+
584
  report += f"Applied surcharge: {self.surcharge:.1f} kPa\n\n"
585
  report += "=" * 70 + "\n"
586
  report += "SETTLEMENT vs TIME:\n"
 
612
  CR=0.30, # Compression ratio
613
  sigma_ini=50.0, # Initial effective stress 50 kPa
614
  sigma_p=80.0, # Preconsolidation pressure 80 kPa
615
+ kh=2.0, # 2 m/year horizontal permeability
616
+ ks=1.0, # 1 m/year smear zone permeability
617
  ),
618
  SoilLayer(
619
  thickness=8.0, # 8 m thick
 
623
  CR=0.35,
624
  sigma_ini=90.0,
625
  sigma_p=90.0,
626
+ kh=1.5, # Lower permeability
627
+ ks=0.75,
628
  ),
629
  SoilLayer(
630
  thickness=7.0, # 7 m thick
 
634
  CR=0.32,
635
  sigma_ini=140.0,
636
  sigma_p=150.0,
637
+ kh=1.8,
638
+ ks=0.9,
639
  ),
640
  ]
641
 
 
645
  ds=0.15, # 150 mm smear zone diameter
646
  De=1.5, # 1.5 m equivalent unit cell diameter (triangular spacing)
647
  L_drain=20.0, # 20 m total drain length (two-way drainage)
648
+ qw=100.0, # 100 m³/year well discharge capacity
 
 
649
  )
650
 
651
  # Applied surcharge
 
720
  CR=layer_data["CR"],
721
  sigma_ini=layer_data["sigma_ini"],
722
  sigma_p=layer_data["sigma_p"],
723
+ kh=layer_data["kh"],
724
+ ks=layer_data["ks"],
725
  )
726
  layers.append(layer)
727
 
 
732
  ds=pvd_data["ds"],
733
  De=pvd_data["De"],
734
  L_drain=pvd_data["L_drain"],
 
 
735
  qw=pvd_data["qw"],
736
  )
737