wolfhece 2.1.124__py3-none-any.whl → 2.1.126__py3-none-any.whl
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.
- wolfhece/PyDraw.py +13 -3
- wolfhece/acceptability/acceptability_gui.py +243 -243
- wolfhece/apps/version.py +1 -1
- wolfhece/lazviewer/laz_viewer.py +107 -22
- wolfhece/math_parser/calculator.py +1 -1
- wolfhece/sigmoid/__init__.py +0 -0
- wolfhece/sigmoid/circle_jax.py +118 -0
- wolfhece/sigmoid/circle_jax_copilot.py +169 -0
- wolfhece/sigmoid/sigmoid.py +776 -0
- wolfhece/wolf_array.py +55 -1
- wolfhece/wolfresults_2D.py +16 -6
- {wolfhece-2.1.124.dist-info → wolfhece-2.1.126.dist-info}/METADATA +1 -1
- {wolfhece-2.1.124.dist-info → wolfhece-2.1.126.dist-info}/RECORD +16 -12
- {wolfhece-2.1.124.dist-info → wolfhece-2.1.126.dist-info}/WHEEL +1 -1
- {wolfhece-2.1.124.dist-info → wolfhece-2.1.126.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.1.124.dist-info → wolfhece-2.1.126.dist-info}/top_level.txt +0 -0
wolfhece/apps/version.py
CHANGED
wolfhece/lazviewer/laz_viewer.py
CHANGED
@@ -17,6 +17,7 @@ from matplotlib.figure import Figure
|
|
17
17
|
from shapely.geometry import LineString, Point, CAP_STYLE, Polygon
|
18
18
|
from shapely.ops import prep
|
19
19
|
import wx
|
20
|
+
from tqdm import tqdm
|
20
21
|
|
21
22
|
from . import viewer
|
22
23
|
from ..PyWMS import getWalonmap
|
@@ -41,6 +42,7 @@ class Colors_Lazviewer(Enum):
|
|
41
42
|
ORTHO_2006_2007 = 2006
|
42
43
|
CODE_2013 = 0
|
43
44
|
CODE_2023 = 2
|
45
|
+
CODE_2021_2022 = 3
|
44
46
|
FROM_FILE = 1
|
45
47
|
|
46
48
|
class Classification_LAZ():
|
@@ -68,6 +70,18 @@ class Classification_LAZ():
|
|
68
70
|
9 : ['Eau', 'Eau',Colors.rgb_withalpha_float('aqua',1.)],
|
69
71
|
10 : ['Ponts', 'Ponts',Colors.rgb_withalpha_float('lightyellow1',1.)]}
|
70
72
|
|
73
|
+
def init_2021_2022(self):
|
74
|
+
|
75
|
+
self.class_name = 'SPW 2021-2022'
|
76
|
+
self.classification={
|
77
|
+
0 : ['0', 'Pas de classification', Colors.rgb_withalpha_float('black',.2),],
|
78
|
+
1 : ['Hors-sol', 'building, toits et autres', Colors.rgb_withalpha_float('white',.2),],
|
79
|
+
2 : ['Sol', 'y compris talus et digues', Colors.rgb_withalpha_float('brown',1.)],
|
80
|
+
4 : ['Végétation', 'y compris la végétation linéaire', Colors.rgb_withalpha_float('forestgreen',1.)],
|
81
|
+
9 : ['Eau', 'Eau',Colors.rgb_withalpha_float('aqua',1.)],
|
82
|
+
10 : ['Ponts', 'Ponts',Colors.rgb_withalpha_float('lightyellow1',1.)],
|
83
|
+
15 : ['Ligne hautes-tension', 'Ligne hautes-tension',Colors.rgb_withalpha_float('lightcyan1',0.25)]}
|
84
|
+
|
71
85
|
def init_2023(self):
|
72
86
|
|
73
87
|
self.class_name = 'SPW-Geofit 2023'
|
@@ -155,8 +169,8 @@ class xyz_laz():
|
|
155
169
|
parts = last_part.split('_')
|
156
170
|
|
157
171
|
# L'origine est codée dans le nom de fichier
|
158
|
-
self.origx = float(parts[
|
159
|
-
self.origy = float(parts[
|
172
|
+
self.origx = float(parts[-3])
|
173
|
+
self.origy = float(parts[-2])
|
160
174
|
|
161
175
|
dx = 2500.
|
162
176
|
dy = 3500.
|
@@ -590,7 +604,8 @@ class xyz_laz_grids():
|
|
590
604
|
dirs = listdir(dir_grids)
|
591
605
|
|
592
606
|
for curdir in dirs:
|
593
|
-
|
607
|
+
if Path(curdir).is_dir():
|
608
|
+
self.grids.append(xyz_laz_grid(join(dir_grids,curdir)))
|
594
609
|
|
595
610
|
def scan_around(self, xy:Union[LineString,list[list[float], list[float]]], length_buffer=5.):
|
596
611
|
"""
|
@@ -707,42 +722,82 @@ class xyz_laz_grids():
|
|
707
722
|
|
708
723
|
return figmpl
|
709
724
|
|
710
|
-
def create_from_laz(self, dir_laz:str, shape:str, ds:float = 50, force_format = np.float64):
|
725
|
+
def create_from_laz(self, dir_laz:str, shape:str=None, ds:float = 50, force_format = np.float64):
|
711
726
|
|
712
727
|
try:
|
713
728
|
from ..PyVertexvectors import Zones
|
714
729
|
except:
|
715
730
|
from wolfhece.PyVertexvectors import Zones
|
716
|
-
vecs = Zones(shape)
|
717
731
|
|
718
|
-
|
732
|
+
vecs = None
|
733
|
+
if shape is not None:
|
734
|
+
vecs = Zones(shape)
|
735
|
+
|
736
|
+
dir_laz = Path(dir_laz)
|
737
|
+
|
738
|
+
for entry in tqdm(dir_laz.glob('**/*.laz')):
|
719
739
|
if entry.is_file():
|
720
|
-
if entry.name.endswith('.laz'):
|
721
740
|
|
722
|
-
|
723
|
-
|
741
|
+
file_wo_suf = entry.stem
|
742
|
+
dirname = join(self.dir, file_wo_suf)
|
724
743
|
|
725
|
-
|
726
|
-
|
744
|
+
if not exists(dirname):
|
745
|
+
makedirs(dirname, exist_ok=True)
|
727
746
|
|
728
|
-
|
747
|
+
newlaz = xyz_laz_grid(mydir=dirname)
|
729
748
|
|
749
|
+
if vecs is not None:
|
730
750
|
vec = vecs.get_zone(file_wo_suf)
|
731
751
|
bounds = vec.myvectors[0].get_bounds()
|
752
|
+
else:
|
753
|
+
lazdata = laspy.read(entry)
|
754
|
+
bounds = [[lazdata.x.min(), lazdata.y.min()],
|
755
|
+
[lazdata.x.max(), lazdata.y.max()]]
|
756
|
+
|
757
|
+
bounds = [[math.floor(bounds[0][0]/ds)*ds, math.floor(bounds[0][1]/ds)*ds],
|
758
|
+
[math.ceil(bounds[1][0]/ds)*ds, math.ceil(bounds[1][1]/ds)*ds]]
|
759
|
+
|
760
|
+
dx = bounds[1][0] -bounds[0][0]
|
761
|
+
dy = bounds[1][1] -bounds[0][1]
|
762
|
+
nb = max(int(dx/ds), int(dy/ds))
|
763
|
+
|
764
|
+
self.grids.append(newlaz._sort_grid_np(entry,
|
765
|
+
join(dirname, file_wo_suf),
|
766
|
+
bounds=[[bounds[0][0], bounds[1][0]], [bounds[0][1], bounds[1][1]]],
|
767
|
+
gridsize=[max(int(dx/ds),1), max(int(dy/ds),1)],
|
768
|
+
force_format=force_format))
|
769
|
+
|
770
|
+
def create_bounds_shape(self, dir_laz:str, out_shape:str):
|
771
|
+
""" Create shape from laz files """
|
772
|
+
try:
|
773
|
+
from ..PyVertexvectors import Zones
|
774
|
+
except:
|
775
|
+
from wolfhece.PyVertexvectors import Zones
|
776
|
+
|
777
|
+
dir_laz = Path(dir_laz)
|
778
|
+
out_shape = Path(out_shape)
|
779
|
+
|
780
|
+
vecs = Zones()
|
781
|
+
|
782
|
+
for entry in tqdm(dir_laz.glob('**/*.laz')):
|
783
|
+
if entry.is_file():
|
784
|
+
lazdata = laspy.read(entry)
|
732
785
|
|
733
|
-
|
734
|
-
|
786
|
+
bounds = [[lazdata.x.min(), lazdata.y.min()],
|
787
|
+
[lazdata.x.max(), lazdata.y.max()]]
|
788
|
+
loczone = zone(name=entry.stem)
|
789
|
+
locvec = vector(name=entry.stem)
|
735
790
|
|
736
|
-
|
737
|
-
|
738
|
-
|
791
|
+
locvec.add_vertex(wolfvertex(bounds[0][0], bounds[0][1]))
|
792
|
+
locvec.add_vertex(wolfvertex(bounds[1][0], bounds[0][1]))
|
793
|
+
locvec.add_vertex(wolfvertex(bounds[1][0], bounds[1][1]))
|
794
|
+
locvec.add_vertex(wolfvertex(bounds[0][0], bounds[1][1]))
|
795
|
+
locvec.close_force()
|
739
796
|
|
740
|
-
|
741
|
-
|
742
|
-
bounds=[[bounds[0][0], bounds[1][0]], [bounds[0][1], bounds[1][1]]],
|
743
|
-
gridsize=[max(int(dx/ds),1), max(int(dy/ds),1)],
|
744
|
-
force_format=force_format))
|
797
|
+
loczone.add_vector(locvec, forceparent=True)
|
798
|
+
vecs.add_zone(loczone, forceparent=True)
|
745
799
|
|
800
|
+
vecs.saveas(Path(self.dir) / out_shape)
|
746
801
|
|
747
802
|
class Wolf_LAZ_Data(Element_To_Draw):
|
748
803
|
""" Base class for LAZ data which can be imported in Pydraw.Mapviewer.
|
@@ -1145,6 +1200,8 @@ class Wolf_LAZ_Data(Element_To_Draw):
|
|
1145
1200
|
logging.warning(_('No classification chosen - Abort !'))
|
1146
1201
|
elif classification == 'SPW 2013-2014':
|
1147
1202
|
self.classification.init_2013()
|
1203
|
+
elif classification == 'SPW 2021-2022':
|
1204
|
+
self.classification.init_2021_2022()
|
1148
1205
|
else:
|
1149
1206
|
self.classification.init_2023()
|
1150
1207
|
|
@@ -1900,6 +1957,34 @@ def get_colors(las:laspy.LasData, which_colors:Colors_Lazviewer, imsize=2000, fn
|
|
1900
1957
|
name, comment, color = value
|
1901
1958
|
colors[myclass==int(key)] = color
|
1902
1959
|
|
1960
|
+
elif which_colors==Colors_Lazviewer.CODE_2021_2022.value:
|
1961
|
+
"""
|
1962
|
+
- Rebus/non classés - Code 0;
|
1963
|
+
- Hors-sol (building, toits et autres) - Code 1;
|
1964
|
+
- Sol (y compris talus et digues) - Code 2;
|
1965
|
+
- Végétation haute (y compris la végétation linéaire) - Code 4;
|
1966
|
+
- Eau - Code 9;
|
1967
|
+
- Pont – Code 10;
|
1968
|
+
- Ligne hautes-tension - Code 15.
|
1969
|
+
"""
|
1970
|
+
if type(las) is laspy.LasData:
|
1971
|
+
myclass = las.classification
|
1972
|
+
elif type(las) is list:
|
1973
|
+
myclass=[]
|
1974
|
+
for curlas in las:
|
1975
|
+
myclass.append(curlas.classification)
|
1976
|
+
myclass = np.concatenate(myclass)
|
1977
|
+
else:
|
1978
|
+
myclass = np.int8(las[:,3])
|
1979
|
+
|
1980
|
+
if palette_classif is None:
|
1981
|
+
palette_classif = Classification_LAZ()
|
1982
|
+
palette_classif.init_2021_2022()
|
1983
|
+
|
1984
|
+
for key, value in palette_classif.classification.items():
|
1985
|
+
name, comment, color = value
|
1986
|
+
colors[myclass==int(key)] = color
|
1987
|
+
|
1903
1988
|
elif which_colors==Colors_Lazviewer.CODE_2023.value:
|
1904
1989
|
|
1905
1990
|
if palette_classif is None:
|
@@ -116,7 +116,7 @@ class Calculator(wx.Frame):
|
|
116
116
|
from ..PyDraw import draw_type
|
117
117
|
ids = self._mapviewer.get_list_keys(drawing_type=draw_type.ARRAYS, checked_state=None)
|
118
118
|
for id in ids:
|
119
|
-
self.memory_txt
|
119
|
+
self.memory_txt = id
|
120
120
|
|
121
121
|
def memory_clear_event(self, e):
|
122
122
|
self.memory_clear()
|
File without changes
|
@@ -0,0 +1,118 @@
|
|
1
|
+
import jax
|
2
|
+
import jax.numpy as jnp
|
3
|
+
from jax import jit, vmap
|
4
|
+
import matplotlib.pyplot as plt
|
5
|
+
import numpy as np
|
6
|
+
|
7
|
+
# Forcer float32
|
8
|
+
jax.config.update("jax_enable_x64", False)
|
9
|
+
|
10
|
+
# Fonction pour calculer l'aire immergée
|
11
|
+
def wet_area(h, R):
|
12
|
+
h = jnp.clip(h, 0, 2 * R)
|
13
|
+
theta = 2 * jnp.arccos(1 - h / R)
|
14
|
+
area = (R**2 / 2) * (theta - jnp.sin(theta))
|
15
|
+
return area
|
16
|
+
|
17
|
+
# Solution "analytique/numérique" classique pour h
|
18
|
+
def analytical_h(R, f):
|
19
|
+
def solve_theta(theta):
|
20
|
+
return (theta - jnp.sin(theta)) / (2 * jnp.pi) - f
|
21
|
+
theta_min, theta_max = jnp.float32(0.0), jnp.float32(2 * jnp.pi)
|
22
|
+
for _ in range(50):
|
23
|
+
theta_mid = (theta_min + theta_max) / 2
|
24
|
+
val = solve_theta(theta_mid)
|
25
|
+
theta_max = jnp.where(val > 0, theta_mid, theta_max)
|
26
|
+
theta_min = jnp.where(val <= 0, theta_mid, theta_min)
|
27
|
+
theta = (theta_min + theta_max) / 2
|
28
|
+
return R * (1 - jnp.cos(theta / 2))
|
29
|
+
|
30
|
+
# Fonction objectif avec sigmoïde adaptative aux deux bornes
|
31
|
+
def objective(h, R, f):
|
32
|
+
total_area = jnp.pi * R**2
|
33
|
+
target_area = f * total_area
|
34
|
+
diff = wet_area(h, R) - target_area
|
35
|
+
# Échelle adaptative : plus kleine pour f près de 0 ou 1
|
36
|
+
scale_factor = jnp.minimum(f, 1. - f) # Distance au bord le plus proche
|
37
|
+
scale = 0.05 * R**2 * jnp.maximum(scale_factor, 0.01) # Éviter 0
|
38
|
+
return 1 / (1 + jnp.exp(-diff / scale))
|
39
|
+
|
40
|
+
# Dichotomie douce améliorée
|
41
|
+
@jit
|
42
|
+
def soft_dichotomy(R, f, max_iter=200):
|
43
|
+
def body(state, _):
|
44
|
+
h_min, h_max = state
|
45
|
+
h_mid = (h_min + h_max) / 2
|
46
|
+
sigmoid_val = objective(h_mid, R, f)
|
47
|
+
h_min_new = h_min + (h_mid - h_min) * (1 - sigmoid_val)
|
48
|
+
h_max_new = h_max - (h_max - h_mid) * sigmoid_val
|
49
|
+
return (h_min_new, h_max_new), None
|
50
|
+
|
51
|
+
# Bornes initiales resserrées pour petits/grands f
|
52
|
+
h_min_init = jnp.float32(0.0)
|
53
|
+
h_max_init = jnp.float32(2 * R)
|
54
|
+
initial_state = (h_min_init, h_max_init)
|
55
|
+
final_state, _ = jax.lax.scan(body, initial_state, None, length=max_iter)
|
56
|
+
h_min, h_max = final_state
|
57
|
+
return (h_min + h_max) / 2
|
58
|
+
|
59
|
+
# Dérivée par rapport à f
|
60
|
+
grad_bisection = jax.grad(soft_dichotomy, argnums=1)
|
61
|
+
|
62
|
+
|
63
|
+
if __name__ == "__main__":
|
64
|
+
# Paramètres
|
65
|
+
R = jnp.float32(1.0)
|
66
|
+
f_values = jnp.linspace(jnp.float32(0.001), jnp.float32(0.999), 500) # Plus près des bords
|
67
|
+
|
68
|
+
# Calculs
|
69
|
+
h_numerical = vmap(lambda f: soft_dichotomy(R, f))(f_values)
|
70
|
+
h_analytical = vmap(lambda f: analytical_h(R, f))(f_values)
|
71
|
+
errors = jnp.abs(h_numerical - h_analytical)
|
72
|
+
|
73
|
+
grads = vmap(lambda f: grad_bisection(R, f))(f_values)
|
74
|
+
|
75
|
+
# Graphiques
|
76
|
+
plt.figure(figsize=(12, 5))
|
77
|
+
|
78
|
+
plt.subplot(1, 3, 1)
|
79
|
+
plt.plot(f_values, h_numerical, label="Numérique (sigmoïde)", color="blue")
|
80
|
+
plt.plot(f_values, h_analytical, "--", label="Analytique", color="orange")
|
81
|
+
plt.xlabel("Fraction immergée (f)")
|
82
|
+
plt.ylabel("Hauteur (h)")
|
83
|
+
plt.title("Hauteur en fonction de f (float32)")
|
84
|
+
plt.legend()
|
85
|
+
plt.grid(True)
|
86
|
+
plt.yscale("log") # Échelle log pour voir les extrêmes
|
87
|
+
|
88
|
+
plt.subplot(1, 3, 2)
|
89
|
+
plt.plot(f_values, errors, color="red")
|
90
|
+
plt.xlabel("Fraction immergée (f)")
|
91
|
+
plt.ylabel("Erreur absolue (|h_num - h_ana|)")
|
92
|
+
plt.title("Erreur par rapport à la solution analytique")
|
93
|
+
plt.grid(True)
|
94
|
+
plt.yscale("log") # Échelle log pour les erreurs
|
95
|
+
|
96
|
+
plt.subplot(1, 3, 3)
|
97
|
+
plt.plot(f_values, grads, label="Dérivée par rapport à f")
|
98
|
+
plt.xlabel("Fraction immergée (f)")
|
99
|
+
plt.ylabel("Gradient de f par rapport à h")
|
100
|
+
plt.legend()
|
101
|
+
plt.grid(True)
|
102
|
+
|
103
|
+
plt.tight_layout()
|
104
|
+
plt.show()
|
105
|
+
|
106
|
+
# Tests spécifiques aux deux bornes
|
107
|
+
for f_test in [0., 0.001, 0.01, 0.5, 0.99, 0.999]:
|
108
|
+
f_test = jnp.float32(f_test)
|
109
|
+
h_num = soft_dichotomy(R, f_test)
|
110
|
+
h_ana = analytical_h(R, f_test)
|
111
|
+
grad_h = grad_bisection(R, f_test)
|
112
|
+
print(f"f = {f_test:.4f}:")
|
113
|
+
print(f" h numérique = {h_num:.6f}")
|
114
|
+
print(f" h analytique = {h_ana:.6f}")
|
115
|
+
print(f" Gradient de h par rapport à f = {grad_h:.6f}")
|
116
|
+
print(f" Erreur = {jnp.abs(h_num - h_ana):.6f}")
|
117
|
+
print(f" Erreur relative = {jnp.abs(h_num - h_ana) / h_ana:.6e}")
|
118
|
+
pass
|
@@ -0,0 +1,169 @@
|
|
1
|
+
import jax
|
2
|
+
import jax.numpy as jnp
|
3
|
+
from jax import jit, vmap
|
4
|
+
import matplotlib.pyplot as plt
|
5
|
+
import numpy as np
|
6
|
+
from scipy.optimize import minimize, root_scalar
|
7
|
+
|
8
|
+
# Forcer float32
|
9
|
+
jax.config.update("jax_enable_x64", False)
|
10
|
+
|
11
|
+
# Fonction pour calculer l'aire immergée
|
12
|
+
def wet_area(h, R):
|
13
|
+
h = jnp.clip(h, 0, 2 * R)
|
14
|
+
theta = 2 * jnp.arccos(1 - h / R)
|
15
|
+
area = (R**2 / 2) * (theta - jnp.sin(theta))
|
16
|
+
return area
|
17
|
+
|
18
|
+
# Solution numérique "classique" pour h
|
19
|
+
def analytical_h(R, f):
|
20
|
+
def solve_theta(theta):
|
21
|
+
return (theta - jnp.sin(theta)) / (2 * jnp.pi) - f
|
22
|
+
theta_min, theta_max = jnp.float32(0.0), jnp.float32(2 * jnp.pi)
|
23
|
+
for _ in range(50):
|
24
|
+
theta_mid = (theta_min + theta_max) / 2
|
25
|
+
val = solve_theta(theta_mid)
|
26
|
+
theta_max = jnp.where(val > 0, theta_mid, theta_max)
|
27
|
+
theta_min = jnp.where(val <= 0, theta_mid, theta_min)
|
28
|
+
theta = (theta_min + theta_max) / 2
|
29
|
+
return R * (1 - jnp.cos(theta / 2))
|
30
|
+
|
31
|
+
# # Fonction objectif avec pondération quadratique
|
32
|
+
# def objective(h, R, f):
|
33
|
+
# total_area = jnp.pi * R**2
|
34
|
+
# target_area = f * total_area
|
35
|
+
# diff = wet_area(h, R) - target_area
|
36
|
+
# scale_factor = jnp.minimum(f, 1. - f)
|
37
|
+
# scale = 0.1 * R**2 * (scale_factor**2 + 0.01) # Échelle quadratique
|
38
|
+
# return 1 / (1 + jnp.exp(-diff / scale))
|
39
|
+
|
40
|
+
# def objective(h, R, f):
|
41
|
+
# total_area = jnp.pi * R**2
|
42
|
+
# target_area = f * total_area
|
43
|
+
# diff = wet_area(h, R) - target_area
|
44
|
+
# scale_factor = jnp.minimum(f, 1. - f)
|
45
|
+
# scale = 0.1 * R**2 * jnp.maximum(scale_factor, 0.01)
|
46
|
+
# return 0.5 * (1 + jnp.tanh(diff / scale))
|
47
|
+
|
48
|
+
|
49
|
+
# Fonction objectif avec lissage (inchangée)
|
50
|
+
def objective(h, R, f):
|
51
|
+
total_area = jnp.pi * R**2
|
52
|
+
target_area = f * total_area
|
53
|
+
diff = wet_area(h, R) - target_area
|
54
|
+
scale_factor = jnp.minimum(f, 1. - f)
|
55
|
+
scale = 0.05 * R**2 * jnp.maximum(scale_factor, 0.01)
|
56
|
+
return 1 / (1 + jnp.exp(-diff / scale))
|
57
|
+
|
58
|
+
# Dichotomie douce avec préservation de la racine
|
59
|
+
@jit
|
60
|
+
def soft_dichotomy(R, f, max_iter=10000):
|
61
|
+
f_sym = jnp.minimum(f, 1 - f)
|
62
|
+
|
63
|
+
def body(state, _):
|
64
|
+
h_min, h_max = state
|
65
|
+
h_mid = (h_min + h_max) / 2
|
66
|
+
sigmoid_val = objective(h_mid, R, f_sym)
|
67
|
+
|
68
|
+
# Différences pour h_min, h_mid et h_max
|
69
|
+
diff_min = wet_area(h_min, R) - f_sym * jnp.pi * R**2
|
70
|
+
diff_max = wet_area(h_max, R) - f_sym * jnp.pi * R**2
|
71
|
+
|
72
|
+
# Facteurs de préservation lisses
|
73
|
+
preserve_min = jnp.exp(-jnp.abs(diff_min) / (0.01)) # Proche de 1 si h_min est racine
|
74
|
+
preserve_max = jnp.exp(-jnp.abs(diff_max) / (0.01)) # Proche de 1 si h_max est racine
|
75
|
+
|
76
|
+
# Mise à jour des bornes avec préservation
|
77
|
+
h_min_new = h_min + (1. - preserve_min) * (h_mid - h_min) * (1. - sigmoid_val)
|
78
|
+
h_max_new = h_max - (1. - preserve_max) * (h_max - h_mid) * sigmoid_val
|
79
|
+
|
80
|
+
# # Garantie que h_min_new < h_max_new
|
81
|
+
# h_min_new = jnp.minimum(h_min_new, h_mid - 0.01 * R)
|
82
|
+
# h_max_new = jnp.maximum(h_max_new, h_mid + 0.01 * R)
|
83
|
+
|
84
|
+
return (h_min_new, h_max_new), None
|
85
|
+
|
86
|
+
h_min_init = jnp.float32(0.0)
|
87
|
+
h_max_init = jnp.float32(2 * R)
|
88
|
+
initial_state = (h_min_init, h_max_init)
|
89
|
+
final_state, _ = jax.lax.scan(body, initial_state, None, length=max_iter)
|
90
|
+
h_min, h_max = final_state
|
91
|
+
h_sym = (h_min + h_max) / 2
|
92
|
+
return jnp.where(f <= 0.5, h_sym, 2 * R - h_sym)
|
93
|
+
|
94
|
+
# Dérivée par rapport à f
|
95
|
+
grad_bisection = jax.grad(soft_dichotomy, argnums=1)
|
96
|
+
|
97
|
+
# Fonction objectif avec lissage
|
98
|
+
def section(h, R, f):
|
99
|
+
total_area = jnp.pi * R**2
|
100
|
+
target_area = f * total_area
|
101
|
+
return wet_area(jnp.clip(h, 0, 2 * R), R) - target_area
|
102
|
+
|
103
|
+
grad_section = jax.grad(section, argnums=0)
|
104
|
+
|
105
|
+
# recherche de la recine de la section
|
106
|
+
def find_root(R, f):
|
107
|
+
def fun(h):
|
108
|
+
return section(h, R, f)
|
109
|
+
def grad(h):
|
110
|
+
return grad_section(h, R, f)
|
111
|
+
|
112
|
+
h_root = root_scalar(fun, fprime=grad, x0=R)
|
113
|
+
return h_root.root
|
114
|
+
|
115
|
+
h = find_root(1.,0.5)
|
116
|
+
pass
|
117
|
+
|
118
|
+
if __name__ == "__main__":
|
119
|
+
R = jnp.float32(1.0)
|
120
|
+
f_values = jnp.linspace(jnp.float32(0.00), jnp.float32(1.), 5000, endpoint=True)
|
121
|
+
|
122
|
+
h_numerical = vmap(lambda f: soft_dichotomy(R, f))(f_values)
|
123
|
+
h_analytical = vmap(lambda f: analytical_h(R, f))(f_values)
|
124
|
+
errors = jnp.abs(h_numerical - h_analytical)
|
125
|
+
|
126
|
+
grads = vmap(lambda f: grad_bisection(R, f))(f_values)
|
127
|
+
|
128
|
+
plt.figure(figsize=(12, 5))
|
129
|
+
|
130
|
+
plt.subplot(1, 3, 1)
|
131
|
+
plt.plot(f_values, h_numerical, label="Numérique (symétrie centrale)", color="blue")
|
132
|
+
plt.plot(f_values, h_analytical, "--", label="Analytique", color="orange")
|
133
|
+
plt.xlabel("Fraction immergée (f)")
|
134
|
+
plt.ylabel("Hauteur (h)")
|
135
|
+
plt.title("Hauteur en fonction de f (float32)")
|
136
|
+
plt.legend()
|
137
|
+
plt.grid(True)
|
138
|
+
# plt.yscale("log")
|
139
|
+
|
140
|
+
plt.subplot(1, 3, 2)
|
141
|
+
plt.plot(f_values, errors, color="red")
|
142
|
+
plt.xlabel("Fraction immergée (f)")
|
143
|
+
plt.ylabel("Erreur absolue (|h_num - h_ana|)")
|
144
|
+
plt.title("Erreur par rapport à la solution analytique")
|
145
|
+
plt.grid(True)
|
146
|
+
plt.yscale("log")
|
147
|
+
|
148
|
+
plt.subplot(1, 3, 3)
|
149
|
+
plt.plot(f_values, grads, label="Dérivée par rapport à f")
|
150
|
+
plt.xlabel("Fraction immergée (f)")
|
151
|
+
plt.ylabel("Gradient de f par rapport à h")
|
152
|
+
plt.legend()
|
153
|
+
plt.grid(True)
|
154
|
+
|
155
|
+
plt.tight_layout()
|
156
|
+
plt.show()
|
157
|
+
|
158
|
+
for f_test in [0., 0.001, 0.01, 0.5, 0.99, 0.999]:
|
159
|
+
f_test = jnp.float32(f_test)
|
160
|
+
h_num = soft_dichotomy(R, f_test)
|
161
|
+
h_ana = analytical_h(R, f_test)
|
162
|
+
grad_h = grad_bisection(R, f_test)
|
163
|
+
print(f"f = {f_test:.4f}:")
|
164
|
+
print(f" h numérique = {h_num:.6f}")
|
165
|
+
print(f" h analytique = {h_ana:.6f}")
|
166
|
+
print(f" Gradient de h par rapport à f = {grad_h:.6f}")
|
167
|
+
print(f" Erreur = {jnp.abs(h_num - h_ana):.6f}")
|
168
|
+
print(f" Erreur relative = {jnp.abs(h_num - h_ana) / h_ana:.6e}")
|
169
|
+
pass
|