diff --git a/example_files/Template_PVtool5_0_SAFE_2022_PV.xlsx b/example_files/Template_PVtool5_0_SAFE_2022_PV.xlsx index f579cd0..b5c7587 100644 Binary files a/example_files/Template_PVtool5_0_SAFE_2022_PV.xlsx and b/example_files/Template_PVtool5_0_SAFE_2022_PV.xlsx differ diff --git a/example_files/WSRL 2025 PVtool5_0_gevalideerd.xlsx b/example_files/WSRL 2025 PVtool5_0_gevalideerd.xlsx new file mode 100644 index 0000000..72aaa74 Binary files /dev/null and b/example_files/WSRL 2025 PVtool5_0_gevalideerd.xlsx differ diff --git a/import/shansep_pdf_export_TXT_SAFE_klei_licht_16_175_TXT_S_POP_15procent_rek.pdf b/import/shansep_pdf_export_TXT_SAFE_klei_licht_16_175_TXT_S_POP_15procent_rek.pdf new file mode 100644 index 0000000..c4d155b Binary files /dev/null and b/import/shansep_pdf_export_TXT_SAFE_klei_licht_16_175_TXT_S_POP_15procent_rek.pdf differ diff --git a/main_nathan.py b/main_nathan.py index 1533e2d..352250c 100644 --- a/main_nathan.py +++ b/main_nathan.py @@ -14,6 +14,8 @@ from pv_tool.imports.validation import Validation from pv_tool.imports.import_data import Dbase from pv_tool.cphi_analysis.c_phi_analysis import CPhiAnalyse +from pv_tool.shansep_analysis.shansep_analysis import SHANSEP +from pv_tool.sutabel_analysis import SUTABEL from pv_tool.imports.import_options import * from typing import Literal @@ -57,42 +59,54 @@ def get_repo_root(root_search_dir: Optional[str] = None) -> str: return repo.working_tree_dir -def test_database_import(source: Literal['Stowa', 'PV-tool', 'Dbase'], +def database_import_test(source: Literal['Stowa', 'PV-tool', 'Dbase'], file_name_import: str, file_name_export: str = 'Template_PVtool5_0.xlsx', short=False, validate=False, export=False): """Test de database import en validatie functionaliteit.""" repo_root = Path(get_repo_root()) path_to_data = repo_root / "example_files" / file_name_import save_test = Path(r"c:\Users\gebraadn0645\ARCADIS\103076457 - STOWA PV Tool - 05 Project execution\Deliverables\2. validatie\Test output") - if short and not validate: - print("Uitvoeren van korte database import test...") - dbase = Dbase() - dbase.import_dbase_short(source=source, source_dir=path_to_data) - if export: - dbase.export_dbase_to_excel(export_dir = save_test, filename=file_name_export) - return dbase - elif validate and not short: - print("Uitvoeren van database import en validatie test...") - dbase = Dbase() - dbase.import_data(source=source, source_dir=path_to_data) - dbase.validate_data(export_path=save_test) - if export: - dbase.export_dbase_to_excel(export_dir = save_test, filename=file_name_export) - return dbase - elif short and validate: - print("Korte import en validatie kan niet samen worden uitgevoerd. Kies één optie.") + # Check if input file exists + if not path_to_data.exists(): + print(f"Error: Input file not found at {path_to_data}") return None - else: - print("Uitvoeren van volledige database import test...") - # Database import - dbase = Dbase() - dbase.import_data(source=source, source_dir=path_to_data) + + # Create save directory if it doesn't exist + save_test.mkdir(parents=True, exist_ok=True) + + dbase = Dbase() + try: + if short and not validate: + print(f"Uitvoeren van korte database import test voor {path_to_data}...") + dbase.import_dbase_short(source=source, source_dir=path_to_data) + elif validate and not short: + print(f"Uitvoeren van database import en validatie test voor {path_to_data}...") + dbase.import_data(source=source, source_dir=path_to_data) + dbase.validate_data(export_path=save_test) + elif short and validate: + print("Korte import en validatie kan niet samen worden uitgevoerd. Kies één optie.") + return None + else: + print(f"Uitvoeren van volledige database import test voor {path_to_data}...") + dbase.import_data(source=source, source_dir=path_to_data) + + # Verify database was loaded successfully + if dbase.dbase_df is None or dbase.dbase_df.empty: + print("Error: Database import failed - dataframe is None or empty") + return None + if export: - dbase.export_dbase_to_excel(export_dir = save_test, filename=file_name_export) + print(f"Exporting database to {save_test / file_name_export}") + dbase.export_dbase_to_excel(export_dir=save_test, filename=file_name_export) + return dbase + except Exception as e: + print(f"Error during database import/export: {str(e)}") + return None + -def test_cphi_analysis_txt(dbase: Dbase, file_name: str = 'Template_PVtool5_0.xlsx'): +def cphi_analysis_txt_test(dbase: Dbase, export_path: Path, export_file: str, plot_extra_dataset = None, plot_spanningspaden=False): """ Test een TXT C-phi analyse. @@ -100,9 +114,11 @@ def test_cphi_analysis_txt(dbase: Dbase, file_name: str = 'Template_PVtool5_0.xl ---------- dbase : Dbase Database instance met testdata + export_path : Path + Directory waar resultaten worden opgeslagen + export_file : str + Naam van het export database bestand """ - save_test = Path(r"c:\Users\gebraadn0645\ARCADIS\103076457 - STOWA PV Tool - 05 Project execution\Deliverables\2. validatie\Test output") - # Initialiseer analyse analyse = CPhiAnalyse( dbase=dbase, @@ -111,21 +127,29 @@ def test_cphi_analysis_txt(dbase: Dbase, file_name: str = 'Template_PVtool5_0.xl analysis_type='TXT_CPhi' ) + # analyse = CPhiAnalyse( + # dbase=dbase, + # investigation_groups=['TXT_SAFE_klei_licht_16_175'], + # effective_stress='eindsterkte', + # analysis_type='TXT_CPhi' + # ) + # Pas instellingen toe analyse.apply_settings(alpha=0.75) - analyse.apply_parameters(cohesie_kar=0.0) + # analyse.apply_parameters(cohesie_gem=6.5, phi_kar=0.45, cohesie_kar=0.1) + analyse.apply_parameters(cohesie_gem=8.0, phi_kar=0.53, cohesie_kar=6.72) # Print en exporteer resultaten print('\nResultaten TXT C-phi analyse:') print(analyse.print_short_results()) - analyse.add_results_to_dbase(path=str(save_test), file_name=file_name) + analyse.add_results_to_dbase(path=str(export_path), file_name=export_file) # Visualisatie - analyse.show_figure() - analyse.save_to_pdf(path=str(save_test)) + analyse.show_figure(plot_extra_dataset = plot_extra_dataset, plot_spanningspaden=plot_spanningspaden) + analyse.save_to_pdf(path=str(export_path)) -def test_cphi_analysis_dss(dbase: Dbase): +def cphi_analysis_dss_test(dbase: Dbase, export_path: Path, export_file: str, plot_extra_dataset = None, plot_spanningspaden=False): """ Test een DSS C-phi analyse. @@ -133,9 +157,11 @@ def test_cphi_analysis_dss(dbase: Dbase): ---------- dbase : Dbase Database instance met testdata + export_path : Path + Directory waar resultaten worden opgeslagen + export_file : str + Naam van het export database bestand """ - save_test = Path(r"c:\Users\gebraadn0645\ARCADIS\103076457 - STOWA PV Tool - 05 Project execution\Deliverables\2. validatie\Test output") - # Initialiseer analyse analyse = CPhiAnalyse( dbase=dbase, @@ -150,14 +176,14 @@ def test_cphi_analysis_dss(dbase: Dbase): # Print en exporteer resultaten print('\nResultaten DSS C-phi analyse:') print(analyse.print_short_results()) - analyse.add_results_to_dbase(path=str(save_test)) + analyse.add_results_to_dbase(path=str(export_path), file_name=export_file) # Visualisatie - analyse.show_figure() - analyse.save_to_pdf(path=str(save_test)) + analyse.show_figure(plot_extra_dataset = plot_extra_dataset, plot_spanningspaden=plot_spanningspaden) + analyse.save_to_pdf(path=str(export_path)) -def test_cphi_analysis_txt_sh(dbase: Dbase): +def cphi_analysis_txt_sh_test(dbase: Dbase, export_path: Path, export_file: str, plot_extra_dataset = None, plot_spanningspaden=False): """ Test een TXT C-phi analyse volgens schematiseringshandleiding (SH). @@ -165,9 +191,11 @@ def test_cphi_analysis_txt_sh(dbase: Dbase): ---------- dbase : Dbase Database instance met testdata + export_path : Path + Directory waar resultaten worden opgeslagen + export_file : str + Naam van het export database bestand """ - save_test = Path(r"c:\Users\gebraadn0645\ARCADIS\103076457 - STOWA PV Tool - 05 Project execution\Deliverables\2. validatie\Test output") - # Initialiseer analyse analyse = CPhiAnalyse( dbase=dbase, @@ -182,14 +210,14 @@ def test_cphi_analysis_txt_sh(dbase: Dbase): # Print en exporteer resultaten print('\nResultaten TXT C-phi analyse (schematiseringshandleiding):') print(analyse.print_short_results()) - analyse.add_results_to_dbase(path=str(save_test)) + analyse.add_results_to_dbase(path=str(export_path), file_name=export_file) # Visualisatie - analyse.show_figure() - analyse.save_to_pdf(path=str(save_test)) + analyse.show_figure(plot_extra_dataset = plot_extra_dataset, plot_spanningspaden=plot_spanningspaden) + analyse.save_to_pdf(path=str(export_path)) -def test_cphi_analysis_dss_sh(dbase: Dbase): +def cphi_analysis_dss_sh_test(dbase: Dbase, export_path: Path, export_file: str, plot_extra_dataset = None, plot_spanningspaden=False): """ Test een DSS C-phi analyse volgens schematiseringshandleiding (SH). @@ -197,9 +225,11 @@ def test_cphi_analysis_dss_sh(dbase: Dbase): ---------- dbase : Dbase Database instance met testdata + export_path : Path + Directory waar resultaten worden opgeslagen + export_file : str + Naam van het export database bestand """ - save_test = Path(r"c:\Users\gebraadn0645\ARCADIS\103076457 - STOWA PV Tool - 05 Project execution\Deliverables\2. validatie\Test output") - # Initialiseer analyse analyse = CPhiAnalyse( dbase=dbase, @@ -214,36 +244,148 @@ def test_cphi_analysis_dss_sh(dbase: Dbase): # Print en exporteer resultaten print('\nResultaten DSS C-phi analyse (schematiseringshandleiding):') print(analyse.print_short_results()) - analyse.add_results_to_dbase(path=str(save_test)) + analyse.add_results_to_dbase(path=str(export_path), file_name=export_file) # Visualisatie - analyse.show_figure() - analyse.save_to_pdf(path=str(save_test)) + analyse.show_figure(plot_extra_dataset = plot_extra_dataset, plot_spanningspaden=plot_spanningspaden) + analyse.save_to_pdf(path=str(export_path)) + + +# function for testing shansep analysis +def shansep_analysis_test(dbase: Dbase, export_path: Path, export_file: str, plot_extra_dataset = None): + """ + Test een SHANSEP analyse. + + Parameters + ---------- + dbase : Dbase + Database instance met testdata + export_path : Path + Directory waar resultaten worden opgeslagen + export_file : str + Naam van het export database bestand + """ + analyse = SHANSEP( + dbase=dbase, + investigation_groups=['DSS_SAFE_veen'], + effective_stress='20% rek', + analysis_type='DSS_S_POP') + + # analyse = SHANSEP( + # dbase=dbase, + # investigation_groups=['TXT_SAFE_klei_licht_16_175'], + # effective_stress='15% rek', + # analysis_type='TXT_S_POP') + + # Pas instellingen toe + analyse.apply_settings(alpha=0.75) + + # Print en exporteer resultaten + print('\nResultaten SHANSEP analyse:') + analyse.add_results_to_dbase(path=str(export_path), file_name=export_file) + + analyse.set_parameters_handmatig(snijpunt_gem=11, s_gem=0.31, m_gem=0.9, snijpunt_kar=7, s_kar=0.28, m_kar=0.9) + + # sutabel = analyse.calculate_sutabel() + analyse.show_figure_sv_su(plot_extra_dataset=plot_extra_dataset) + analyse.show_figure_ln_ocr_ln_s(plot_extra_dataset=plot_extra_dataset) + analyse.show_figure_sv_su_nc(plot_extra_dataset=plot_extra_dataset) + pdf_path = analyse.save_to_pdf(path=str(export_path)) + analyse.save_total_to_excel(path=str(export_path)) + + +def sutabel_analysis_test(dbase: Dbase, export_path: Path, export_file: str, plot_extra_dataset = None): + """ + Test een SUTABEL analyse. + + Parameters + ---------- + dbase : Dbase + Database instance met testdata + export_path : Path + Directory waar resultaten worden opgeslagen + export_file : str + Naam van het export database bestand + """ + sutabel = SUTABEL( + dbase=dbase, + analysis_type='DSS_su_tabel', + investigation_groups=['DSS_SAFE_veen'], + effective_stress='20% rek' + ) + + # sutabel = SUTABEL( + # dbase=dbase, + # analysis_type='TXT_su_tabel', + # investigation_groups=['TXT_SAFE_klei_licht_16_175'], + # effective_stress='15% rek' + # ) + + sutabel.apply_settings(alpha=0.75) + + sutabel.set_manual_parameters(a2_kar=0.683, a1_kar=0.489, vc_fit_kar=0.1) + + # Visualize (analysis runs automatically) + sutabel.show_figure_ln_sv_ln_su_sutabel(plot_extra_dataset=plot_extra_dataset) + sutabel.show_figure_sv_su_sutabel(plot_extra_dataset=plot_extra_dataset) + + # Export (analysis runs automatically if needed) + sutabel.add_results_to_dbase(str(export_path), export_file) + sutabel.save_to_pdf(str(export_path)) if __name__ == "__main__": # Test database import - source = 'PV-tool' # Opties: 'Stowa', 'PV-tool', 'Dbase' - import_name = 'WSRL 2025 Proevenverzameling_tool_v4.2n_gevalideerd_nieuw4.xlsm' - export_name = 'Template_PVtool5_0_WSRL_2025_PV.xlsx' - import_name2 = export_name # TODO eventueel: import naam kan nu niet export naam zijn, dan kan die niet de layout aanpassen. Moet nog worden aangepast in de toekomst. + source = 'Dbase' # Opties: 'Stowa', 'PV-tool', 'Dbase' + import_name = 'Template_PVtool5_0_SAFE_2022_PV.xlsx' + # import_name = 'WSRL 2025 PVtool5_0_gevalideerd.xlsx' + export_name = 'Template_PVtool5_0_SAFE_2022_PV_aangepast.xlsx' + export_dir = Path(r"c:\Users\gebraadn0645\ARCADIS\103076457 - STOWA PV Tool - 05 Project execution\Deliverables\2. validatie\Test output") + export_dir.mkdir(parents=True, exist_ok=True) + print("Start van de tests...\n") - dbase = test_database_import(source=source, file_name_import=import_name, file_name_export=export_name, short=False, validate=False, export=True) + print(f"Using source: {source}") + print(f"Import file: {import_name}") + print(f"Export file: {export_name}") + print(f"Export directory: {export_dir}") + + dbase = database_import_test( + source=source, + file_name_import=import_name, + file_name_export=export_name, + short=True, + validate=False, + export=True + ) + + if dbase is None or dbase.dbase_df is None: + print("ERROR: Database import gefaald!") + exit(1) + + plot_extra_dataset = None + plot_spanningspaden = False # Test verschillende analyses print("\nUitvoeren van verschillende test cases...") - print("\n1. TXT C-phi analyse test") - # file_name = 'Template_PVtool5_0_SAFE_2022_PV.xlsx' - # test_cphi_analysis_txt(dbase, file_name=file_name) - - # print("\n2. DSS C-phi analyse test") - # test_cphi_analysis_dss(dbase) - # - # print("\n3. TXT C-phi analyse test (schematiseringshandleiding)") - # test_cphi_analysis_txt_sh(dbase) - # - # print("\n4. DSS C-phi analyse test (schematiseringshandleiding)") - # test_cphi_analysis_dss_sh(dbase) + print("\nTXT C-phi analyse test") + cphi_analysis_txt_test(dbase, export_dir, export_name, plot_extra_dataset=plot_extra_dataset, plot_spanningspaden=plot_spanningspaden) + + print("\nDSS C-phi analyse test") + cphi_analysis_dss_test(dbase, export_dir, export_name, plot_extra_dataset=plot_extra_dataset, plot_spanningspaden=plot_spanningspaden) + + print("\nTXT C-phi analyse (schematiseringshandleiding) test") + cphi_analysis_txt_sh_test(dbase, export_dir, export_name, plot_extra_dataset=plot_extra_dataset, plot_spanningspaden=plot_spanningspaden) + + print("\nDSS C-phi analyse (schematiseringshandleiding) test") + cphi_analysis_dss_sh_test(dbase, export_dir, export_name, plot_extra_dataset=plot_extra_dataset, plot_spanningspaden=plot_spanningspaden) + + plot_extra_dataset = ['TXT_SAFE_klei_zwaar'] + + print("\nTXT SHANSEP analyse test") + shansep_analysis_test(dbase, export_dir, export_name, plot_extra_dataset=plot_extra_dataset) + + print("\nSUTABEL analyse test") + sutabel_analysis_test(dbase, export_dir, export_name, plot_extra_dataset=plot_extra_dataset) print("\nAlle tests zijn voltooid!") diff --git a/pv_tool/cphi_analysis/c_phi_analysis.py b/pv_tool/cphi_analysis/c_phi_analysis.py index a6cafbb..d3124bd 100644 --- a/pv_tool/cphi_analysis/c_phi_analysis.py +++ b/pv_tool/cphi_analysis/c_phi_analysis.py @@ -35,7 +35,7 @@ class CPhiAnalyse: Klasse voor het uitvoeren van c-phi analyses op grondmonsters. Ondersteunt zowel triaxiaal (TXT) als direct simple shear (DSS) testen, - en kan zowel reguliere c-phi als shansep (SH) analyses uitvoeren. + en kan zowel reguliere c-phi als schematiseringshandleiding (SH) analyses uitvoeren. """ def __init__(self, dbase: Dbase, @@ -122,6 +122,8 @@ def __init__(self, dbase: Dbase, self.figure = go.Figure() self.show_title: Optional[bool] = True + # ========= Instelling en Data Ophalen Methodes ========== + def get_cphi_data(self): """ Filtert de database op basis van analysetype en proefgroepen. @@ -133,7 +135,7 @@ def get_cphi_data(self): if self.analysis_type in ['TXT_CPhi', 'TXT_SH']: self.cphi_analyses_data_df = self.dbase_df[self.dbase_df['ALG__TRIAXIAAL']] self.cphi_analyses_data_df = self.cphi_analyses_data_df[self.cphi_analyses_data_df['PV_NAAM'].isin( - self.investigation_groups)] # TODO should have selectie veranderen in de dbase en daarmee verder + self.investigation_groups)] self.calc_watergehalte_gem = calc_watergehalte_gem(self) self.calc_watergehalte_sd = calc_watergehalte_sd(self) self.calc_vgwnat_gem = calc_vgwnat_gem(self) @@ -141,10 +143,17 @@ def get_cphi_data(self): self.total_cphi_analyses_data_df = self.cphi_analyses_data_df self.cphi_analyses_data_df = self.cphi_analyses_data_df[TEXTUAL_NAMES.get(self.effective_stress, [])] + # Valideer of er data overblijft na filtering + if self.cphi_analyses_data_df.empty: + raise ValueError(f"Geen data gevonden na filtering op investigation_groups {self.investigation_groups} " + f"en effective_stress '{self.effective_stress}' voor analyse type '{self.analysis_type}'") + + print(f"Data na filtering: {len(self.cphi_analyses_data_df)} rijen gevonden") + elif self.analysis_type in ['DSS_CPhi', 'DSS_SH']: self.cphi_analyses_data_df = self.dbase_df[self.dbase_df['ALG__DSS']] self.cphi_analyses_data_df = self.cphi_analyses_data_df[self.cphi_analyses_data_df['PV_NAAM'].isin( - self.investigation_groups)] # TODO should have selectie veranderen in de dbase en daarmee verder + self.investigation_groups)] self.calc_watergehalte_gem = calc_watergehalte_gem(self) self.calc_watergehalte_sd = calc_watergehalte_sd(self) self.calc_vgwnat_gem = calc_vgwnat_gem(self) @@ -152,6 +161,13 @@ def get_cphi_data(self): self.total_cphi_analyses_data_df = self.cphi_analyses_data_df self.cphi_analyses_data_df = self.cphi_analyses_data_df[TEXTUAL_NAMES_DSS.get(self.effective_stress, [])] + # Valideer of er data overblijft na filtering + if self.cphi_analyses_data_df.empty: + raise ValueError(f"Geen data gevonden na filtering op investigation_groups {self.investigation_groups} " + f"en effective_stress '{self.effective_stress}' voor analyse type '{self.analysis_type}'") + + print(f"Data na filtering: {len(self.cphi_analyses_data_df)} rijen gevonden") + self.cphi_analyses_data_df.columns = NEW_COLUMN_NAMES def apply_settings(self, alpha: Optional[float] = None, @@ -309,21 +325,23 @@ def plot_spanningspaden(self): else: raise ValueError("Geen geldige data gevonden voor de spanningspaden.") - def get_previous_results(self, path: str): + def get_previous_results(self, path: str, file_name: str): """ Zoekt naar eerdere analyseresultaten in een Excel-bestand. Parameters ---------- - path : str + path: str Pad naar de map waar het Excel-bestand staat + file_name: str + Naam van het Excel-bestand Returns ------- DataFrame of None DataFrame met eerdere resultaten als deze gevonden zijn, anders None """ - file_name = 'Template_PVtool5_0.xlsx' + file_path = f"{path}/{file_name}" try: @@ -333,9 +351,9 @@ def get_previous_results(self, path: str): raise FileNotFoundError("Er is geen dbase aanwezig onder de naam Template_PVtool5_0.xlsx") try: - results_df = read_excel(file_path, sheet_name='Resultaten') + results_df = read_excel(file_path, sheet_name='Resultaten c-phi') except ValueError: - print("Er is geen tabblad 'Resultaten' aanwezig in het Excel-bestand.") + print("Er is geen tabblad 'Resultaten c-phi' aanwezig in het Excel-bestand.") return None filtered_df = results_df[ @@ -352,6 +370,8 @@ def get_previous_results(self, path: str): return latest_entry + # ========= Analyse Methodes ========== + def expand_analysis_df(self): """ Berekent afgeleide parameters voor de c-phi analyse. @@ -371,7 +391,7 @@ def expand_analysis_df(self): def expand_analysis_df_sh(self): """ - Berekent afgeleide parameters voor de shansep analyse. + Berekent afgeleide parameters voor de schematiseringshandleiding analyse. Voegt kolommen toe aan het dataframe met berekende waarden voor tan(alpha) en ln(tan(alpha)). @@ -406,6 +426,8 @@ def expand_analysis_df_corrected(self): calculate_s_ty_ondergrens_correctie_c(self) calculate_kappa_2_ondergrens_correctie_c(self) + # ========= Resultaten Methodes ========== + def result_values(self): """ Berekent de definitieve resultaten van de c-phi analyse: gemiddelde, karakteristieke en rekenwaarden voor @@ -436,7 +458,7 @@ def result_values(self): def result_values_sh(self): """ - Berekent de definitieve resultaten van de shansep analyse: + Berekent de definitieve resultaten van de c-phi schematiseringshandleiding analyse: gemiddelde, karakteristieke en rekenwaarden voor phi, inclusief standaarddeviatie. @@ -470,13 +492,15 @@ def _run(self): def _run_sh(self): """ - Voert de volledige shansep analyse uit in de juiste volgorde: + Voert de volledige c-phi schematiseringshandleiding analyse uit in de juiste volgorde: data ophalen, parameters berekenen en resultaten bepalen. """ self.get_cphi_data() self.expand_analysis_df_sh() self.result_values_sh() + # ========== Visualisatie Methodes ========== + def set_figure(self, plot_extra_dataset: Optional[List] = None, plot_spanningspaden: bool = False): """ Maakt een visualisatie van de analyseresultaten. @@ -555,11 +579,13 @@ def print_short_results(self): analyse_output_df['cohesie [kPa]'] = [self.c_gem, self.c_kar, self.c_d, self.st_dev_c] return analyse_output_df + # ========== Export Methodes ========== + def add_results_to_dbase(self, path, file_name: str = 'Template_PVtool5_0.xlsx'): """ Voegt analyseresultaten toe aan de database export. - Voegt de resultaten toe aan een tabblad 'Resultaten' in de Template_PVtool5_0.xlsx. + Voegt de resultaten toe aan een tabblad 'Resultaten c-phi' in de Template_PVtool5_0.xlsx. Als het tabblad al bestaat wordt het aangevuld, anders wordt het aangemaakt. Parameters @@ -632,30 +658,30 @@ def add_results_to_dbase(self, path, file_name: str = 'Template_PVtool5_0.xlsx') workbook = load_workbook(file_path) - if 'Resultaten' in workbook.sheetnames: - print('Tabblad resultaten in dbase excel bestaat al en wordt aangevuld') - df_existing = read_excel(file_path, sheet_name='Resultaten') + if 'Resultaten c-phi' in workbook.sheetnames: + print('Tabblad Resultaten c-phi in dbase excel bestaat al en wordt aangevuld') + df_existing = read_excel(file_path, sheet_name='Resultaten c-phi') # Filter out empty rows and ensure consistent types before concatenation df_existing = df_existing.dropna(how='all') new_row_df = DataFrame([new_row], columns=df_existing.columns) df_updated = concat([df_existing, new_row_df], ignore_index=True) else: - print('Tabblad resultaten in dbase excel bestaat nog niet en wordt aangemaakt') + print('Tabblad Resultaten c-phi in dbase excel bestaat nog niet en wordt aangemaakt') df_updated = DataFrame([new_row], columns=expected_columns) # Write data to Excel with ExcelWriter(file_path, engine='openpyxl', mode='a', if_sheet_exists='replace') as writer: - df_updated.to_excel(writer, sheet_name='Resultaten', index=False) + df_updated.to_excel(writer, sheet_name='Resultaten c-phi', index=False) # Formatting num_columns = df_updated.shape[1] num_rows = df_updated.shape[0] format_excel_sheet( file_path=file_path, - sheet_name='Resultaten', + sheet_name='Resultaten c-phi', num_columns=num_columns, num_rows=num_rows, - table_name='ResultatenTable', + table_name='Resultaten_CPHI_Table', index=False # Changed from True to False to match the to_excel call ) diff --git a/pv_tool/cphi_analysis/calc_parameters.py b/pv_tool/cphi_analysis/calc_parameters.py index bdf4fac..518bed4 100644 --- a/pv_tool/cphi_analysis/calc_parameters.py +++ b/pv_tool/cphi_analysis/calc_parameters.py @@ -8,11 +8,10 @@ if TYPE_CHECKING: from pv_tool.cphi_analysis.c_phi_analysis import CPhiAnalyse -from pv_tool.cphi_analysis.variables import (count_s, sum_s, sum_t, e_a2, a2_kar, a1_kar, t_n_2, t_n_2_sh, +from pv_tool.cphi_analysis.variables import (count_s, sum_s, sum_t, e_a2, a2_kar, a1_kar, t_n_2_sh, gem_ln_tan_a_sh, std_ln_tan_a_sh, a2_kar_gecorrigeerd, a1_kar_gecorrigeerd, helling_gecor, var_tan_phi_gem, - var_tan_phi_kar, var_tan_phi_kar_sh, var_a2_gem_sh, var_a2_boven_sh, - var_a2_onder_sh) + var_tan_phi_kar, var_tan_phi_kar_sh) def calc_watergehalte_gem(self: CPhiAnalyse): """Berekent het gemiddelde watergehalte van de geselecteerde monsters [%].""" @@ -126,7 +125,7 @@ def calc_c_gem(self: CPhiAnalyse): elif self.analysis_type == 'DSS_CPhi': return float(coh_gem) else: - print('WARNING: analysis type SH does not include cohesion') + print('WAARSCHUWING: analysetype SH bevat geen cohesie') def calc_phi_kar(self: CPhiAnalyse): @@ -152,7 +151,7 @@ def calc_c_kar(self: CPhiAnalyse): elif self.analysis_type == 'DSS_CPhi': return float(coh_kar) else: - print('WARNING: analysis type SH does not include cohesion') + print('WAARSCHUWING: analysetype SH bevat geen cohesie') def calc_phi_d(self: CPhiAnalyse): @@ -222,7 +221,7 @@ def calc_tan_phi_kar(self: CPhiAnalyse): elif self.analysis_type == 'DSS_CPhi': return float(phi_kar) else: - print('WARNING: tan phi kar for analysis type in SH is calculated with the function calc_tan_phi_kar_sh') + print('WAARSCHUWING: tan phi kar voor analysetype SH wordt berekend met de functie calc_tan_phi_kar_sh') def calc_tan_phi_kar_sh(self: CPhiAnalyse): """Berekent de karakteristieke tan(phi) voor shansep analyses.""" @@ -231,4 +230,4 @@ def calc_tan_phi_kar_sh(self: CPhiAnalyse): elif self.analysis_type == 'DSS_SH': return float(calc_a2_phi_kar_onder_sh(self)) else: - print('WARNING: tan phi kar for analysis type in CPhi is calculated with the function calc_tan_phi_kar') + print('WAARSCHUWING: tan phi kar voor analysetype CPhi wordt berekend met de functie calc_tan_phi_kar') diff --git a/pv_tool/cphi_analysis/expand_analysis_df.py b/pv_tool/cphi_analysis/expand_analysis_df.py index c8f0706..e0b10fb 100644 --- a/pv_tool/cphi_analysis/expand_analysis_df.py +++ b/pv_tool/cphi_analysis/expand_analysis_df.py @@ -1,6 +1,5 @@ from __future__ import annotations from typing import TYPE_CHECKING -import numpy as np if TYPE_CHECKING: from pv_tool.cphi_analysis.c_phi_analysis import CPhiAnalyse @@ -8,14 +7,12 @@ def calculate_tan_a(self: CPhiAnalyse): """Berekent de tangens van hoek a voor alle rijen in de DataFrame.""" - # Create a copy to avoid chained assignment df = self.cphi_analyses_data_df.copy() df.loc[:, 'tan(a)'] = df['T'] / df['S\''] self.cphi_analyses_data_df = df def calculate_ln_tan_a(self: CPhiAnalyse): """Berekent de natuurlijke logaritme van tan(a) voor alle rijen in de DataFrame.""" - # Create a copy to avoid chained assignment df = self.cphi_analyses_data_df.copy() df.loc[:, 'LN(tan(a))'] = df['tan(a)'].apply(lambda x: np.log(x) if x is not None and x > 0 else "") self.cphi_analyses_data_df = df diff --git a/pv_tool/cphi_analysis/globals.py b/pv_tool/cphi_analysis/globals.py index 5c52626..891bfda 100644 --- a/pv_tool/cphi_analysis/globals.py +++ b/pv_tool/cphi_analysis/globals.py @@ -35,7 +35,6 @@ # -------------------------------------- DSS --------------------------------------- CONSOLIDATIE_COLUMNS_DSS = ['PV_NAAM', 'DSS_EFF_VERT_SPANNING_EINDE_CONSOLIDATIE', 'DSS_T_CONSOLIDATIE'] -# TODO die laatste kolomnaam klopt niet - check bij Leo welke kolom erbij hoort TWO_PERC_COLUMNS_DSS = ['PV_NAAM', 'DSS_S_2%', 'DSS_T_2%'] diff --git a/pv_tool/cphi_analysis/save_and_export.py b/pv_tool/cphi_analysis/save_and_export.py index e77baf0..05ef37c 100644 --- a/pv_tool/cphi_analysis/save_and_export.py +++ b/pv_tool/cphi_analysis/save_and_export.py @@ -5,7 +5,7 @@ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.enums import TA_LEFT from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, LongTable - +import plotly.graph_objects as go if TYPE_CHECKING: from pv_tool.cphi_analysis.c_phi_analysis import CPhiAnalyse @@ -244,7 +244,8 @@ def save_to_pdf(self: "CPhiAnalyse", path: str) -> str: fig_path = f"{path}/temp_plot.png" if not hasattr(self, 'figure') or len(self.figure.data) == 0: self.show_title = False - self.show_figure() + self.figure = go.Figure() + self.set_figure() self.show_title = True fig_width = 1280 diff --git a/pv_tool/cphi_analysis/variables.py b/pv_tool/cphi_analysis/variables.py index 3aea9af..3c9de38 100644 --- a/pv_tool/cphi_analysis/variables.py +++ b/pv_tool/cphi_analysis/variables.py @@ -168,8 +168,30 @@ def helling_gecor(self: CPhiAnalyse): if self.cohesie_gem_handmatig is not None: x_values = self.cphi_analyses_data_df['S\''] y_values = self.cphi_analyses_data_df['correctie_t'] - x = np.array(x_values)[:, np.newaxis] - helling, _, _, _ = np.linalg.lstsq(x, np.array(y_values)) + + # Valideer of er voldoende data is + if len(x_values) == 0 or len(y_values) == 0: + raise ValueError(f"Onvoldoende data voor helling berekening. Aantal datapunten: {len(x_values)}") + + if len(x_values) != len(y_values): + raise ValueError(f"Dimensie mismatch: x_values heeft {len(x_values)} elementen, y_values heeft {len(y_values)} elementen") + + # Controleer op NaN waarden + x_clean = x_values.dropna() + y_clean = y_values.dropna() + + if len(x_clean) < 2 or len(y_clean) < 2: + raise ValueError(f"Onvoldoende geldige datapunten voor regressie. Geldige x: {len(x_clean)}, geldige y: {len(y_clean)}") + + # Zorg ervoor dat we de juiste indices gebruiken + valid_indices = x_values.notna() & y_values.notna() + if valid_indices.sum() < 2: + raise ValueError(f"Onvoldoende geldige datapunt paren voor regressie: {valid_indices.sum()}") + + x_array = np.array(x_values[valid_indices])[:, np.newaxis] + y_array = np.array(y_values[valid_indices]) + + helling, residuals, rank, singular_values = np.linalg.lstsq(x_array, y_array, rcond=None) return float(helling[0]) else: helling = sum_s_ty(self) / sum_s_tt(self) diff --git a/pv_tool/imports/add_ana_columns.py b/pv_tool/imports/add_ana_columns.py index 39b612f..8a1de66 100644 --- a/pv_tool/imports/add_ana_columns.py +++ b/pv_tool/imports/add_ana_columns.py @@ -213,6 +213,59 @@ def calculate_row(row): else: self.dbase_df['OCR_TXT'] = None +# def calc_ocr_txt(self: Dbase): +# """ +# Deze functie berekent de OCR van de triaxiaalproeven volgens de formule: +# MAX(TXT_SS_OCR, (MAX(ANA_GRENSSPANNING_INCL_CORRECTIE, ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING) / +# (TXT_SS_S''_EIND_CONSOLIDATIE + TXT_SS_T_EIND_CONSOLIDATIE))) +# """ +# def calculate_row(row): +# try: +# # Get the first part: TXT_SS_OCR +# ocr_txt = row.get('TXT_SS_OCR', None) +# rownr = row.get('ALG__REGEL', None) +# if rownr == 1201: +# print('ocr_txt:', ocr_txt) +# # Only proceed with calculation if any of these tests are True +# if any([row.get('ALG__CRS', False), +# row.get('ALG__SAMENDRUKKING', False), +# row.get('ALG__DSS', False), +# row.get('ALG__TRIAXIAAL', False)]): +# if rownr == 1201: +# print('proceeding with calculation for row 1201') +# +# # Get maximum of grensspanning and max verticale consolidatie spanning +# grensspanning = row.get('ANA_GRENSSPANNING_INCL_CORRECTIE', None) +# max_vert_consol = row.get('ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', None) +# numerator = max(filter(None, [grensspanning, max_vert_consol])) if any(x is not None for x in [grensspanning, max_vert_consol]) else None +# +# # Get denominator components +# s_eind = row.get("TXT_SS_S'_EIND_CONSOLIDATIE", 0) +# t_eind = row.get('TXT_SS_T_EIND_CONSOLIDATIE', 0) +# denominator = s_eind + t_eind if (s_eind is not None and t_eind is not None) else None +# +# if rownr == 1201: +# print('proceeding with calculation for row 1201') +# print('numerator:', numerator) +# print('denominator:', denominator) +# +# # Calculate the ratio if possible +# if numerator is not None and denominator is not None and denominator != 0: +# calculated_ocr = numerator / denominator +# +# # Return maximum of TXT_SS_OCR and calculated OCR +# if ocr_txt is not None: +# return max(ocr_txt, calculated_ocr) +# return calculated_ocr +# +# # If no calculation possible but TXT_SS_OCR exists, return that +# return ocr_txt if ocr_txt is not None else None +# +# except Exception: +# return None +# +# self.dbase_df['OCR_TXT'] = self.dbase_df.apply(calculate_row, axis=1) + def calc_ocr_dss(self: Dbase): """Deze functie berekent de OCR van de DSS-proeven.""" diff --git a/pv_tool/imports/import_data.py b/pv_tool/imports/import_data.py index be58608..a404416 100644 --- a/pv_tool/imports/import_data.py +++ b/pv_tool/imports/import_data.py @@ -104,14 +104,14 @@ def export_dbase_to_excel(self, export_dir: Path, filename: str = 'Template_PVto export_df = self.dbase_df[final_columns].copy() # rond waarden af voor consistentie - cols_to_round = ['ANA_TERREINSPANNING', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', - 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_GRENSSPANNING_PROEF', - 'ANA_POP_VELD', 'ANA_POP_VELD_GEMIDDELD', 'ANA_GRENSSPANNING_VOORSTEL', - 'ANA_GRENSSPANNING_REKEN', 'OCR_TXT', 'OCR_DSS'] - numeric_columns = export_df.select_dtypes(include=['float64', 'int64']).columns - for col in cols_to_round: - if col in numeric_columns: - export_df[col] = export_df[col].round(2) + # cols_to_round = ['ANA_TERREINSPANNING', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', + # 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_GRENSSPANNING_PROEF', + # 'ANA_POP_VELD', 'ANA_POP_VELD_GEMIDDELD', 'ANA_GRENSSPANNING_VOORSTEL', + # 'ANA_GRENSSPANNING_REKEN', 'OCR_TXT', 'OCR_DSS'] + # numeric_columns = export_df.select_dtypes(include=['float64', 'int64']).columns + # for col in cols_to_round: + # if col in numeric_columns: + # export_df[col] = export_df[col].round(2) timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') print(f"Excel sheet Dbase5_0 wordt weggeschreven op {timestamp}") @@ -136,7 +136,7 @@ def export_dbase_to_excel(self, export_dir: Path, filename: str = 'Template_PVto float_format="%.6f" # Use consistent float format ) - print(f"Excel file exported to: {export_path}") + print(f"Excel bestand geëxporteerd naar: {export_path}") # Formatting num_columns = export_df.shape[1] diff --git a/pv_tool/imports/validation.py b/pv_tool/imports/validation.py index 8b72553..ad78581 100644 --- a/pv_tool/imports/validation.py +++ b/pv_tool/imports/validation.py @@ -107,7 +107,7 @@ def validate_with_schema(self, category, schema: Schema): missing_columns = [col for col in schema_columns if col not in df.columns] if missing_columns: - print(f"Missing columns in category '{category}': {', '.join(missing_columns)}") + print(f"Ontbrekende kolommen in categorie '{category}': {', '.join(missing_columns)}") available_columns = [col for col in schema_columns if col in df.columns] data_to_validate = df[available_columns] @@ -248,7 +248,7 @@ def validation_log(self, export_path: Path, critical: Optional[bool] = True): index=True ) except Exception as format_error: - print(f"Warning: Could not format sheet {sheet_name}: {str(format_error)}") + print(f"Waarschuwing: Kon sheet {sheet_name} niet formatteren: {str(format_error)}") # Continue with other sheets even if one fails to format except Exception as e: diff --git a/pv_tool/shansep_analysis/calc_parameters.py b/pv_tool/shansep_analysis/calc_parameters.py index 3b7ed4b..dafe407 100644 --- a/pv_tool/shansep_analysis/calc_parameters.py +++ b/pv_tool/shansep_analysis/calc_parameters.py @@ -1,31 +1,234 @@ -import math +from pandas import DataFrame import numpy as np -from scipy.stats import linregress, norm -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from pv_tool.shansep_analysis.shansep_analysis import SHANSEP - -def calc_watergehalte_gem(self: SHANSEP): - """Geeft gemiddelde watergehalte bij de geselecteerde pvnaam""" - column_name = self.shansep_data_df.filter(like='WATERGEHALTE_VOOR').columns[0] - watergehalte = self.shansep_data_df[column_name] - return watergehalte.mean() - -def calc_watergehalte_sd(self: SHANSEP): - """Geeft standaard deviatie watergehalte bij de geselecteerde pvnaam""" - column_name = self.shansep_data_df.filter(like='WATERGEHALTE_VOOR').columns[0] - watergehalte = self.shansep_data_df[column_name] - return watergehalte.std() - -def calc_vgwnat_gem(self: SHANSEP): - """Geeft gemiddelde nat volumegewicht bij de geselecteerde pvnaam [[[VOLUMEGEWICHT_NAT]]]""" - column_name = self.shansep_data_df.filter(like='VOLUMEGEWICHT_NAT').columns[0] - vgwnat = self.shansep_data_df[column_name] - return vgwnat.mean() - -def calc_vgwnat_sd(self: SHANSEP): - """Geeft standaard deviatie nat volumegewicht bij de geselecteerde pvnaam""" - column_name = self.shansep_data_df.filter(like='VOLUMEGEWICHT_NAT').columns[0] - vgwnat = self.shansep_data_df[column_name] - return vgwnat.std() \ No newline at end of file + + +def calc_watergehalte_gem_txt(df: DataFrame) -> float: + """ + Berekent het gemiddelde watergehalte voor TXT analyses. + + Parameters + ---------- + df : DataFrame + DataFrame met TXT data + + Returns + ------- + float + Gemiddelde watergehalte of NaN als geen geldige data beschikbaar is + """ + try: + if 'TXT_SS_WATERGEHALTE_VOOR' not in df.columns: + return np.nan + + watergehalte = df['TXT_SS_WATERGEHALTE_VOOR'] + + numeric_values = watergehalte.dropna() + numeric_values = numeric_values[numeric_values != ''] + numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna() + + return numeric_values.mean() if len(numeric_values) > 0 else np.nan + except Exception: + return np.nan + + +def calc_watergehalte_sd_txt(df: DataFrame) -> float: + """ + Berekent de standaarddeviatie van het watergehalte voor TXT analyses. + + Parameters + ---------- + df : DataFrame + DataFrame met TXT data + + Returns + ------- + float + Standaarddeviatie watergehalte of NaN als geen geldige data beschikbaar is + """ + try: + if 'TXT_SS_WATERGEHALTE_VOOR' not in df.columns: + return np.nan + + watergehalte = df['TXT_SS_WATERGEHALTE_VOOR'] + + numeric_values = watergehalte.dropna() + numeric_values = numeric_values[numeric_values != ''] + numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna() + + return numeric_values.std() if len(numeric_values) > 1 else np.nan + except Exception: + return np.nan + + +def calc_vgwnat_gem_txt(df: DataFrame) -> float: + """ + Berekent het gemiddelde volumegewicht nat voor TXT analyses. + + Parameters + ---------- + df : DataFrame + DataFrame met TXT data + + Returns + ------- + float + Gemiddelde volumegewicht nat of NaN als geen geldige data beschikbaar is + """ + try: + if 'TXT_SS_VOLUMEGEWICHT_NAT' not in df.columns: + return np.nan + + vgwnat = df['TXT_SS_VOLUMEGEWICHT_NAT'] + + numeric_values = vgwnat.dropna() + numeric_values = numeric_values[numeric_values != ''] + numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna() + + return numeric_values.mean() if len(numeric_values) > 0 else np.nan + except Exception: + return np.nan + + +def calc_vgwnat_sd_txt(df: DataFrame) -> float: + """ + Berekent de standaarddeviatie van het volumegewicht nat voor TXT analyses. + + Parameters + ---------- + df : DataFrame + DataFrame met TXT data + + Returns + ------- + float + Standaarddeviatie volumegewicht nat of NaN als geen geldige data beschikbaar is + """ + try: + if 'TXT_SS_VOLUMEGEWICHT_NAT' not in df.columns: + return np.nan + + vgwnat = df['TXT_SS_VOLUMEGEWICHT_NAT'] + + numeric_values = vgwnat.dropna() + numeric_values = numeric_values[numeric_values != ''] + numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna() + + return numeric_values.std() if len(numeric_values) > 1 else np.nan + except Exception: + return np.nan + + +def calc_watergehalte_gem_dss(df: DataFrame) -> float: + """ + Berekent het gemiddelde watergehalte voor DSS analyses. + + Parameters + ---------- + df : DataFrame + DataFrame met DSS data + + Returns + ------- + float + Gemiddelde watergehalte of NaN als geen geldige data beschikbaar is + """ + try: + if 'DSS_WATERGEHALTE_VOOR' not in df.columns: + return np.nan + + watergehalte = df['DSS_WATERGEHALTE_VOOR'] + + numeric_values = watergehalte.dropna() + numeric_values = numeric_values[numeric_values != ''] + numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna() + + return numeric_values.mean() if len(numeric_values) > 0 else np.nan + except Exception: + return np.nan + + +def calc_watergehalte_sd_dss(df: DataFrame) -> float: + """ + Berekent de standaarddeviatie van het watergehalte voor DSS analyses. + + Parameters + ---------- + df : DataFrame + DataFrame met DSS data + + Returns + ------- + float + Standaarddeviatie watergehalte of NaN als geen geldige data beschikbaar is + """ + try: + if 'DSS_WATERGEHALTE_VOOR' not in df.columns: + return np.nan + + watergehalte = df['DSS_WATERGEHALTE_VOOR'] + + numeric_values = watergehalte.dropna() + numeric_values = numeric_values[numeric_values != ''] + numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna() + + return numeric_values.std() if len(numeric_values) > 1 else np.nan + except Exception: + return np.nan + + +def calc_vgwnat_gem_dss(df: DataFrame) -> float: + """ + Berekent het gemiddelde volumegewicht nat voor DSS analyses. + + Parameters + ---------- + df : DataFrame + DataFrame met DSS data + + Returns + ------- + float + Gemiddelde volumegewicht nat of NaN als geen geldige data beschikbaar is + """ + try: + if 'DSS_VOLUMEGEWICHT_NAT' not in df.columns: + return np.nan + + vgwnat = df['DSS_VOLUMEGEWICHT_NAT'] + + numeric_values = vgwnat.dropna() + numeric_values = numeric_values[numeric_values != ''] + numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna() + + return numeric_values.mean() if len(numeric_values) > 0 else np.nan + except Exception: + return np.nan + + +def calc_vgwnat_sd_dss(df: DataFrame) -> float: + """ + Berekent de standaarddeviatie van het volumegewicht nat voor DSS analyses. + + Parameters + ---------- + df : DataFrame + DataFrame met DSS data + + Returns + ------- + float + Standaarddeviatie volumegewicht nat of NaN als geen geldige data beschikbaar is + """ + try: + if 'DSS_VOLUMEGEWICHT_NAT' not in df.columns: + return np.nan + + vgwnat = df['DSS_VOLUMEGEWICHT_NAT'] + + numeric_values = vgwnat.dropna() + numeric_values = numeric_values[numeric_values != ''] + numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna() + + return numeric_values.std() if len(numeric_values) > 1 else np.nan + except Exception: + return np.nan diff --git a/pv_tool/shansep_analysis/expand_analysis.py b/pv_tool/shansep_analysis/expand_analysis.py index 6e25447..eadd7d0 100644 --- a/pv_tool/shansep_analysis/expand_analysis.py +++ b/pv_tool/shansep_analysis/expand_analysis.py @@ -3,25 +3,33 @@ if TYPE_CHECKING: from pv_tool.shansep_analysis.shansep_analysis import SHANSEP -from pv_tool.shansep_analysis.variables import * +from pv_tool.shansep_analysis.variables import (count_sv_oc, sum_sv_oc, e_a1_oc, e_a2_oc, + t_n_2_oc, sigma_a1_oc, sigma_a2_oc, rho_a1_a2_oc, + sum_chi_2_oc, count_s_eff_oc, sum_s_eff_oc, + count_ln_ocr_nc_oc, sum_ln_ocr_nc_oc, e_a1_nc_oc, e_a2_nc_oc, + t_n_2_nc_oc, sigma_a1_nc_oc, sigma_a2_nc_oc, rho_a1_a2_nc_oc, + sum_chi_2_nc_oc, count_s_eff_nc_oc, sum_s_eff_nc_oc, + a1_kar_oc, a2_kar_oc, + a1_kar_nc_oc, a2_kar_nc_oc) +import numpy as np # -------------------------- algemeen --------------------------------------- def calculate_ln_ocr(self: SHANSEP): - self.shansep_data_df['LN(OCR)'] = ( + self.shansep_data_df.loc[:, 'LN(OCR)'] = ( self.shansep_data_df['OCR'].apply (lambda x: np.log(x) if x is not None and x > 0 else "")) def calculate_sv_spop(self: SHANSEP): - self.shansep_data_df['S'] = self.shansep_data_df['Su'] / self.shansep_data_df['S\'v'] + self.shansep_data_df.loc[:, 'S'] = self.shansep_data_df['Su'] / self.shansep_data_df['S\'v'] def calculate_ln_sv_spop(self: SHANSEP): - self.shansep_data_df['LN(su/svc)'] = ( + self.shansep_data_df.loc[:, 'LN(su/svc)'] = ( self.shansep_data_df['S'].apply (lambda x: np.log(x) if x is not None and x > 0 else "")) def calculate_pop(self: SHANSEP): - self.shansep_data_df['POP'] = ((self.shansep_data_df['terreinspanning'] * + self.shansep_data_df.loc[:, 'POP'] = ((self.shansep_data_df['terreinspanning'] * self.shansep_data_df['OCR']) - self.shansep_data_df['terreinspanning']) @@ -38,9 +46,9 @@ def calculate_sv_ty_oc(self: SHANSEP): df['s_ty'] = (df['S\'v'] - mean_s) * df['Su'] self.shansep_data_df_oc = df -def calculate_kappa_2_oc(self: SHANSEP): +def calculate_chi_2_oc(self: SHANSEP): formule = (self.shansep_data_df_oc['Su'] - e_a1_oc(self) - e_a2_oc(self) * self.shansep_data_df_oc['S\'v']) ** 2 - self.shansep_data_df_oc['kappa_2'] = formule + self.shansep_data_df_oc['chi_2'] = formule def s_min_oc(self: SHANSEP): return self.shansep_data_df_oc['S\'v'].min() @@ -64,7 +72,7 @@ def calculate_5pr_ondergrens_oc(self: SHANSEP): e_a2_oc(self) * self.shansep_data_df_oc['s\''] - t_n_2_oc(self) * (sigma_a1_oc(self) ** 2 + self.shansep_data_df_oc['s\''] ** 2 * sigma_a2_oc(self) ** 2 + 2 * rho_a1_a2_oc(self) * self.shansep_data_df_oc['s\''] * sigma_a1_oc(self) * sigma_a2_oc(self) + - (1.0 - self.alpha) * (sum_kappa_2_oc(self) / (count_sv_oc(self) - 2))) ** 0.5 + (1.0 - self.alpha) * (sum_chi_2_oc(self) / (count_sv_oc(self) - 2))) ** 0.5 ) self.shansep_data_df_oc['5_pr_ondergrens'] = formule @@ -75,7 +83,7 @@ def calculate_5pr_bovengrens_oc(self: SHANSEP): e_a2_oc(self) * self.shansep_data_df_oc['s\''] + t_n_2_oc(self) * (sigma_a1_oc(self) ** 2 + self.shansep_data_df_oc['s\''] ** 2 * sigma_a2_oc(self) ** 2 + 2 * rho_a1_a2_oc(self) * self.shansep_data_df_oc['s\''] * sigma_a1_oc(self) * sigma_a2_oc(self) + - (1 - self.alpha) * (sum_kappa_2_oc(self) / (count_sv_oc(self) - 2))) ** 0.5 + (1 - self.alpha) * (sum_chi_2_oc(self) / (count_sv_oc(self) - 2))) ** 0.5 ) self.shansep_data_df_oc['5_pr_bovengrens'] = formule @@ -91,41 +99,39 @@ def calculate_sv_ty_ondergrens_oc(self: SHANSEP): self.shansep_data_df_oc['s_ty_ondergrens'] = formule -def calculate_kappa_2_ondergrens_oc(self: SHANSEP): +def calculate_chi_2_ondergrens_oc(self: SHANSEP): formule = (self.shansep_data_df_oc['5_pr_ondergrens'] - a1_kar_oc(self) - a2_kar_oc(self) * self.shansep_data_df_oc['s\'']) ** 2 - self.shansep_data_df_oc['kappa_2_ondergrens'] = formule + self.shansep_data_df_oc['chi_2_ondergrens'] = formule # -------------------------- NC en OC --------------------------------------- def calculate_sv_tt_nc_oc(self: SHANSEP): df = self.shansep_data_df_nc_oc.copy() - df['s_tt'] = (df['LN(OCR)'] - sum_sv_nc_oc(self) / count_sv_nc_oc(self)) ** 2 + df['s_tt'] = (df['LN(OCR)'] - sum_ln_ocr_nc_oc(self) / count_ln_ocr_nc_oc(self)) ** 2 self.shansep_data_df_nc_oc = df def calculate_sv_ty_nc_oc(self: SHANSEP): df = self.shansep_data_df_nc_oc.copy() - mean_s = sum_sv_nc_oc(self) / count_sv_nc_oc(self) + mean_s = sum_ln_ocr_nc_oc(self) / count_ln_ocr_nc_oc(self) df['s_ty'] = (df['LN(OCR)'] - mean_s) * df['LN(su/svc)'] self.shansep_data_df_nc_oc = df -# TODO vanaf hier verder met aanpassen namen variabelen en functies - -def calculate_kappa_2_nc_oc(self: SHANSEP): - formule = (self.shansep_data_df_nc_oc['Su'] - e_a1_nc_oc(self) - e_a2_nc_oc(self) * self.shansep_data_df_nc_oc['S\'v']) ** 2 - self.shansep_data_df_nc_oc['kappa_2'] = formule +def calculate_chi_2_nc_oc(self: SHANSEP): + formule = (self.shansep_data_df_nc_oc['LN(su/svc)'] - e_a1_nc_oc(self) - e_a2_nc_oc(self) * self.shansep_data_df_nc_oc['LN(OCR)']) ** 2 + self.shansep_data_df_nc_oc['chi_2'] = formule def s_min_nc_oc(self: SHANSEP): - return self.shansep_data_df_nc_oc['S\'v'].min() + return self.shansep_data_df_nc_oc['LN(OCR)'].min() def s_max_nc_oc(self: SHANSEP): - return self.shansep_data_df_nc_oc['S\'v'].max() + return self.shansep_data_df_nc_oc['LN(OCR)'].max() def calculate_sv_eff_nc_oc(self: SHANSEP): """Berekent s' waarden met gelijke intervallen tussen min en max.""" lijst = [s_min_nc_oc(self)] - formule = (s_max_nc_oc(self) - s_min_nc_oc(self)) / (count_sv_nc_oc(self) - 1) + formule = (s_max_nc_oc(self) - s_min_nc_oc(self)) / (count_ln_ocr_nc_oc(self) - 1) aantal_waarden = len(self.shansep_data_df_nc_oc) for i in range(1, aantal_waarden): nieuwe_waarde = lijst[i - 1] + formule @@ -138,8 +144,9 @@ def calculate_5pr_ondergrens_nc_oc(self: SHANSEP): e_a2_nc_oc(self) * self.shansep_data_df_nc_oc['s\''] - t_n_2_nc_oc(self) * (sigma_a1_nc_oc(self) ** 2 + self.shansep_data_df_nc_oc['s\''] ** 2 * sigma_a2_nc_oc(self) ** 2 + 2 * rho_a1_a2_nc_oc(self) * self.shansep_data_df_nc_oc['s\''] * sigma_a1_nc_oc(self) * sigma_a2_nc_oc(self) + - (1.0 - self.alpha) * (sum_kappa_2_nc_oc(self) / (count_sv_nc_oc(self) - 2))) ** 0.5 + (1.0 - self.alpha) * (sum_chi_2_nc_oc(self) / (count_ln_ocr_nc_oc(self) - 2))) ** 0.5 ) + self.shansep_data_df_nc_oc['5_pr_ondergrens'] = formule @@ -149,7 +156,7 @@ def calculate_5pr_bovengrens_nc_oc(self: SHANSEP): e_a2_nc_oc(self) * self.shansep_data_df_nc_oc['s\''] + t_n_2_nc_oc(self) * (sigma_a1_nc_oc(self) ** 2 + self.shansep_data_df_nc_oc['s\''] ** 2 * sigma_a2_nc_oc(self) ** 2 + 2 * rho_a1_a2_nc_oc(self) * self.shansep_data_df_nc_oc['s\''] * sigma_a1_nc_oc(self) * sigma_a2_nc_oc(self) + - (1 - self.alpha) * (sum_kappa_2_nc_oc(self) / (count_sv_nc_oc(self) - 2))) ** 0.5 + (1 - self.alpha) * (sum_chi_2_nc_oc(self) / (count_ln_ocr_nc_oc(self) - 2))) ** 0.5 ) self.shansep_data_df_nc_oc['5_pr_bovengrens'] = formule @@ -165,7 +172,7 @@ def calculate_sv_ty_ondergrens_nc_oc(self: SHANSEP): self.shansep_data_df_nc_oc['s_ty_ondergrens'] = formule -def calculate_kappa_2_ondergrens_nc_oc(self: SHANSEP): +def calculate_chi_2_ondergrens_nc_oc(self: SHANSEP): formule = (self.shansep_data_df_nc_oc['5_pr_ondergrens'] - a1_kar_nc_oc(self) - a2_kar_nc_oc(self) * self.shansep_data_df_nc_oc['s\'']) ** 2 - self.shansep_data_df_nc_oc['kappa_2_ondergrens'] = formule \ No newline at end of file + self.shansep_data_df_nc_oc['chi_2_ondergrens'] = formule \ No newline at end of file diff --git a/pv_tool/shansep_analysis/globals.py b/pv_tool/shansep_analysis/globals.py index 6ccb175..785e968 100644 --- a/pv_tool/shansep_analysis/globals.py +++ b/pv_tool/shansep_analysis/globals.py @@ -1,15 +1,15 @@ #------------------------------------SHANSEP---------------------------------------- #------------------------------------TXT----------------------------------------- -TWO_PERC_COLUMNS = ['PV_NAAM', 'TXT_SS_T_2%', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING'] +TWO_PERC_COLUMNS = ['PV_NAAM', 'ALG__REGEL', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'TXT_SS_T_2%', 'ANA_TXT_CONSOLIDATIE_TYPE_REKEN', 'OCR_TXT', 'ANA_TERREINSPANNING'] -FIVE_PERC_COLUMNS = ['PV_NAAM', 'TXT_SS_T_5%', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING'] +FIVE_PERC_COLUMNS = ['PV_NAAM', 'ALG__REGEL', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING','TXT_SS_T_5%', 'ANA_TXT_CONSOLIDATIE_TYPE_REKEN', 'OCR_TXT', 'ANA_TERREINSPANNING'] -FIFTEEN_PERC_COLUMNS = ['PV_NAAM', 'TXT_SS_T_15%', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING'] +FIFTEEN_PERC_COLUMNS = ['PV_NAAM', 'ALG__REGEL', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'TXT_SS_T_15%', 'ANA_TXT_CONSOLIDATIE_TYPE_REKEN', 'OCR_TXT', 'ANA_TERREINSPANNING'] -PIEKSTERKTE_COLUMNS = ['PV_NAAM', 'TXT_SS_T_BIJ_T_PIEK', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING'] +PIEKSTERKTE_COLUMNS = ['PV_NAAM', 'ALG__REGEL', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'TXT_SS_T_BIJ_T_PIEK', 'ANA_TXT_CONSOLIDATIE_TYPE_REKEN', 'OCR_TXT', 'ANA_TERREINSPANNING'] -EINDSTERKTE_COLUMNS = ['PV_NAAM', 'TXT_SS_T_BIJ_T_EIND', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING'] +EINDSTERKTE_COLUMNS = ['PV_NAAM', 'ALG__REGEL', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'TXT_SS_T_BIJ_T_EIND', 'ANA_TXT_CONSOLIDATIE_TYPE_REKEN', 'OCR_TXT', 'ANA_TERREINSPANNING'] TEXTUAL_NAMES = { '2% rek': TWO_PERC_COLUMNS, @@ -19,23 +19,23 @@ 'eindsterkte': EINDSTERKTE_COLUMNS, } -NEW_COLUMN_NAMES = ['PV_NAAM', 'S\'v', 'Su', 'consolidatietype', 'OCR', 'terreinspanning'] +NEW_COLUMN_NAMES = ['PV_NAAM', 'regel nr', 'S\'v', 'Su', 'consolidatietype', 'OCR', 'terreinspanning'] # -------------------------------------- DSS --------------------------------------- -TWO_PERC_COLUMNS_DSS = ['PV_NAAM', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_2%', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING'] +TWO_PERC_COLUMNS_DSS = ['PV_NAAM', 'ALG__REGEL', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_2%', 'ANA_DSS_CONSOLIDATIE_TYPE_REKEN', 'OCR_DSS', 'ANA_TERREINSPANNING'] -FIVE_PERC_COLUMNS_DSS = ['PV_NAAM', 'DSS_T_5%', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING'] +FIVE_PERC_COLUMNS_DSS = ['PV_NAAM', 'ALG__REGEL', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_5%', 'ANA_DSS_CONSOLIDATIE_TYPE_REKEN', 'OCR_DSS', 'ANA_TERREINSPANNING'] -TEN_PERC_COLUMNS_DSS = ['PV_NAAM', 'DSS_T_10%', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING'] +TEN_PERC_COLUMNS_DSS = ['PV_NAAM', 'ALG__REGEL', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_10%', 'ANA_DSS_CONSOLIDATIE_TYPE_REKEN', 'OCR_DSS', 'ANA_TERREINSPANNING'] -FIFTEEN_PERC_COLUMNS_DSS = ['PV_NAAM', 'DSS_T_15%', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING'] +FIFTEEN_PERC_COLUMNS_DSS = ['PV_NAAM', 'ALG__REGEL', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_15%', 'ANA_DSS_CONSOLIDATIE_TYPE_REKEN', 'OCR_DSS', 'ANA_TERREINSPANNING'] -TWENTY_PERC_COLUMNS_DSS = ['PV_NAAM', 'DSS_T_20%', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING'] +TWENTY_PERC_COLUMNS_DSS = ['PV_NAAM', 'ALG__REGEL', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_20%', 'ANA_DSS_CONSOLIDATIE_TYPE_REKEN', 'OCR_DSS', 'ANA_TERREINSPANNING'] -PIEKSTERKTE_COLUMNS_DSS = ['PV_NAAM', 'DSS_T_BIJ_T_MAX', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING'] +PIEKSTERKTE_COLUMNS_DSS = ['PV_NAAM', 'ALG__REGEL', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_BIJ_T_MAX', 'ANA_DSS_CONSOLIDATIE_TYPE_REKEN', 'OCR_DSS', 'ANA_TERREINSPANNING'] -EINDSTERKTE_COLUMNS_DSS = ['PV_NAAM', 'DSS_T_BIJ_T_EIND', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING'] +EINDSTERKTE_COLUMNS_DSS = ['PV_NAAM', 'ALG__REGEL', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_BIJ_T_EIND', 'ANA_DSS_CONSOLIDATIE_TYPE_REKEN', 'OCR_DSS', 'ANA_TERREINSPANNING'] TEXTUAL_NAMES_DSS = { '2% rek': TWO_PERC_COLUMNS_DSS, diff --git a/pv_tool/shansep_analysis/save_and_export.py b/pv_tool/shansep_analysis/save_and_export.py new file mode 100644 index 0000000..f4bd66a --- /dev/null +++ b/pv_tool/shansep_analysis/save_and_export.py @@ -0,0 +1,907 @@ +from typing import TYPE_CHECKING, List +from pandas import ExcelWriter, concat, DataFrame, read_excel +from datetime import datetime +from openpyxl import load_workbook +from reportlab.lib.pagesizes import A4, landscape +from reportlab.lib import colors +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.lib.enums import TA_LEFT +from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, LongTable +from pv_tool.imports.excel_utils import format_excel_sheet +import plotly.graph_objects as go +import numpy as np + +try: + from PIL import Image as PILImage + PIL_AVAILABLE = True +except ImportError: + PIL_AVAILABLE = False + print("Waarschuwing: PIL (Pillow) is niet beschikbaar. Figuren kunnen niet aan PDF worden toegevoegd.") + +if TYPE_CHECKING: + from pv_tool.shansep_analysis.shansep_analysis import SHANSEP + + + +def add_results_to_dbase(self: "SHANSEP", path: str, file_name: str = 'Template_PVtool5_0.xlsx'): + """ + Voegt de SHANSEP analyseresultaten toe aan de database Excel-bestand. + + Parameters + ---------- + self : SHANSEP + Instantie van de SHANSEP analyse klasse + path : str + Pad naar de map waar het Excel-bestand staat + file_name : str + Naam van het Excel-bestand + + Returns + ------- + DataFrame + Bijgewerkte DataFrame met alle resultaten + """ + file_path = f"{path}/{file_name}" + + # Run analysis to get results + df_gem, df_kar = self.get_result_values_shansep() + + # Expected columns structure voor SHANSEP resultaten + expected_columns = [ + 'PVNAAM', 'PV_REK', 'PV_TYPE_PROEF', 'PV_ANALYSE', 'PV_RESULTAAT_ID', 'PV_TYPEVERZAMELING', + 'PV_A1_SNIJPUNT_YAS_GEM [-]', 'PV_A2_S_GEM [-]', 'PV_m_GEM [-]', 'PV_POP_GEM [kPa]', + 'PV_A1_SNIJPUNT_YAS_KAR [-]', 'PV_A2_S_KAR [-]', 'PV_m_KAR [-]', 'PV_POP_KAR [kPa]', + 'PV_S_SD_DSTAB [-]', 'PV_m_SD_DSTAB [-]', 'PV_POP_SD_DSTAB [-]', + 'PV_VGWNAT_GEM [kN/m3]', 'PV_VGWNAT_SD [kN/m3]', 'PV_WATERGEHALTE_GEM', 'PV_WATERGEHALTE_SD', + 'Timestamp' + ] + + new_row = { + 'PVNAAM': self.investigation_groups[0], + 'PV_REK': self.effective_stress, + 'PV_TYPE_PROEF': self.analysis_type.split('_')[0], + 'PV_ANALYSE': '_'.join(self.analysis_type.split('_')[1:]), + 'PV_RESULTAAT_ID': f"{self.investigation_groups[0]}_{self.effective_stress}_{self.analysis_type}", + 'PV_TYPEVERZAMELING': self.alpha, + 'PV_A1_SNIJPUNT_YAS_GEM [-]': round(self.snijpunt_gem_handmatig, 3) if self.snijpunt_gem_handmatig is not None else None, + 'PV_A2_S_GEM [-]': round(self.s_gem_handmatig, 3) if self.s_gem_handmatig is not None else None, + 'PV_m_GEM [-]': round(self.m_gem_handmatig, 3) if self.m_gem_handmatig is not None else None, + 'PV_POP_GEM [kPa]': round(self.pop_gem_handmatig, 3) if self.pop_gem_handmatig is not None else None, + 'PV_A1_SNIJPUNT_YAS_KAR [-]': round(self.snijpunt_kar_handmatig, 3) if self.snijpunt_kar_handmatig is not None else None, + 'PV_A2_S_KAR [-]': round(self.s_kar_handmatig, 3) if self.s_kar_handmatig is not None else None, + 'PV_m_KAR [-]': round(self.m_kar_handmatig, 3) if self.m_kar_handmatig is not None else None, + 'PV_POP_KAR [kPa]': round(self.pop_kar_handmatig, 3) if self.pop_kar_handmatig is not None else None, + 'PV_S_SD_DSTAB [-]': round(self.st_dev_s_handmatig, 3) if self.st_dev_s_handmatig is not None else None, + 'PV_m_SD_DSTAB [-]': round(self.st_dev_m_handmatig, 3) if self.st_dev_s_handmatig is not None else None, + 'PV_POP_SD_DSTAB [-]': round(self.st_dev_pop_handmatig, 3) if self.st_dev_pop_handmatig is not None else None, + 'PV_VGWNAT_GEM [kN/m3]': round(self.calc_vgwnat_gem, 3) if self.calc_vgwnat_gem is not None else None, + 'PV_VGWNAT_SD [kN/m3]': round(self.calc_vgwnat_sd, 3) if self.calc_vgwnat_sd is not None else None, + 'PV_WATERGEHALTE_GEM': round(self.calc_watergehalte_gem, 3) if self.calc_watergehalte_gem is not None else None, + 'PV_WATERGEHALTE_SD': round(self.calc_watergehalte_sd, 3) if self.calc_watergehalte_sd is not None else None, + 'Timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + + workbook = load_workbook(file_path) + + if 'Resultaten SHANSEP' in workbook.sheetnames: + print('Tabblad resultaten SHANSEP in dbase excel bestaat al en wordt aangevuld') + df_existing = read_excel(file_path, sheet_name='Resultaten SHANSEP') + # Filter out empty rows and ensure consistent types before concatenation + df_existing = df_existing.dropna(how='all') + # Ensure column headers are strings + df_existing.columns = df_existing.columns.astype(str) + new_row_df = DataFrame([new_row], columns=df_existing.columns) + df_updated = concat([df_existing, new_row_df], ignore_index=True) + else: + print('Tabblad resultaten SHANSEP in dbase excel bestaat nog niet en wordt aangemaakt') + df_updated = DataFrame([new_row], columns=expected_columns) + + # Ensure all column headers are strings + df_updated.columns = df_updated.columns.astype(str) + + # Write data to Excel + with ExcelWriter(file_path, engine='openpyxl', mode='a', if_sheet_exists='replace') as writer: + df_updated.to_excel(writer, sheet_name='Resultaten SHANSEP', index=False) + + # Formatting + num_columns = df_updated.shape[1] + num_rows = df_updated.shape[0] + format_excel_sheet( + file_path=file_path, + sheet_name='Resultaten SHANSEP', + num_columns=num_columns, + num_rows=num_rows, + table_name='ResultatenSHANSEPTable', + index=False + ) + + return df_updated + + +def save_total_to_excel(self: "SHANSEP", path: str): + """ + Exporteert alle SHANSEP analysegegevens naar Excel. + + Slaat de volledige dataset met alle berekende kolommen op in een Excel bestand. + De bestandsnaam wordt automatisch gegenereerd op basis van de analyse-instellingen. + + Parameters + ---------- + path : str + Map locatie waar het Excel-bestand moet worden opgeslagen + self : SHANSEP + Instantie van de SHANSEP klasse + """ + + # Pas de effective stress naam aan zodat het weggeschreven kan worden in de bestandsnaam + effective_stress = str(self.effective_stress).replace('%', 'procent_') + effective_stress = str(effective_stress).replace(' ', '') + + # Exporteer onder de juiste naam + file_name = f"shansep_export_{self.investigation_groups[0]}_{self.analysis_type}_{effective_stress}.xlsx" + file_path = f"{path}/{file_name}" + + # Run analysis to ensure data is available + self._run_shansep() + + # Get results + df_gem, df_kar = self.get_result_values_shansep() + + # Ensure all column headers are strings + if self.shansep_data_df_oc is not None: + self.shansep_data_df_oc.columns = self.shansep_data_df_oc.columns.astype(str) + if self.shansep_data_df_nc_oc is not None: + self.shansep_data_df_nc_oc.columns = self.shansep_data_df_nc_oc.columns.astype(str) + if hasattr(self, 'sutabel') and self.sutabel is not None: + self.sutabel.columns = self.sutabel.columns.astype(str) + + # Ensure result DataFrames have string columns and index + df_gem.columns = df_gem.columns.astype(str) + df_kar.columns = df_kar.columns.astype(str) + + # Ensure index is also string type + df_gem.index = df_gem.index.astype(str) + df_kar.index = df_kar.index.astype(str) + + # Set index names to avoid Excel adding 'Column1' header + df_gem.index.name = 'analyse' + df_kar.index.name = 'analyse' + + # Write all data to Excel + with ExcelWriter(file_path, engine='openpyxl') as writer: + # Main analysis data + if self.shansep_data_df_oc is not None: + self.shansep_data_df_oc.to_excel(writer, sheet_name='Analyse Data OC', index=False) + if self.shansep_data_df_nc_oc is not None: + self.shansep_data_df_nc_oc.to_excel(writer, sheet_name='Analyse Data OC en NC', index=False) + + # Results - clean data before export + df_gem_export = df_gem.copy() + df_kar_export = df_kar.copy() + + # Replace None values with empty string to avoid Excel issues + df_gem_export = df_gem_export.fillna('') + df_kar_export = df_kar_export.fillna('') + + df_gem_export.to_excel(writer, sheet_name='Resultaten Gemiddeld', index=True) + df_kar_export.to_excel(writer, sheet_name='Resultaten Karakteristiek', index=True) + + # Su tabel if available + if hasattr(self, 'sutabel') and self.sutabel is not None: + self.sutabel.to_excel(writer, sheet_name='Su Tabel', index=False) + + # format the sheets + if self.shansep_data_df_oc is not None: + format_excel_sheet( + file_path=file_path, + sheet_name='Analyse Data OC', + num_columns=self.shansep_data_df_oc.shape[1], + num_rows=self.shansep_data_df_oc.shape[0], + table_name='AnalyseDataOCTable', + index=False + ) + if self.shansep_data_df_nc_oc is not None: + format_excel_sheet( + file_path=file_path, + sheet_name='Analyse Data OC en NC', + num_columns=self.shansep_data_df_nc_oc.shape[1], + num_rows=self.shansep_data_df_nc_oc.shape[0], + table_name='AnalyseDataNC_OCTable', + index=False + ) + format_excel_sheet( + file_path=file_path, + sheet_name='Resultaten Gemiddeld', + num_columns=df_gem.shape[1], # +1 for index + num_rows=df_gem.shape[0], + table_name='ResultatenGemiddeldTable', + index=True + ) + format_excel_sheet( + file_path=file_path, + sheet_name='Resultaten Karakteristiek', + num_columns=df_kar.shape[1], # +1 for index + num_rows=df_kar.shape[0], + table_name='ResultatenKarakteristiekTable', + index=True + ) + if self.sutabel is not None: + format_excel_sheet( + file_path=file_path, + sheet_name='Su Tabel', + num_columns=self.sutabel.shape[1], + num_rows=self.sutabel.shape[0], + table_name='SuTabelTable', + index=False + ) + + print(f"SHANSEP Excel export voltooid: {file_path}") + + +def _df_to_table_with_index(df, index_name='Index'): + """ + Zet een DataFrame om naar een lijst voor gebruik in een PDF tabel. + + Parameters + ---------- + df : DataFrame + De DataFrame die moet worden omgezet + index_name : str, optioneel + Naam voor de index kolom (default='Index') + + Returns + ------- + list + Lijst met header en data rijen voor een PDF tabel + """ + header = [df.index.name or index_name] + df.columns.tolist() + data = [[idx] + row.tolist() for idx, row in df.iterrows()] + return [header] + data + + +def _create_input_table(self: "SHANSEP") -> Table: + """ + Maakt een tabel met de invoerselectie informatie voor SHANSEP analyse. + + Returns + ------- + Table + ReportLab tabel object met de invoerselectie informatie + """ + if self.shansep_data_df_nc_oc_unsorted is None: + return Table([['Geen invoerdata beschikbaar']], hAlign='LEFT') + + # Selecteer relevante kolommen + columns_base = ['PV_NAAM', 'BORING_POSITIE', 'MONSTER_NIVEAU_NAP_VANAF', 'MONSTER_NIVEAU_NAP_TOT', + 'ANA_TERREINSPANNING', 'ANA_GRENSSPANNING_REKEN', 'ANA_POP_VELD'] + + if self.analysis_type.startswith('TXT'): + columns_extra = ['TXT_SS_VOLUMEGEWICHT_NAT', 'TXT_SS_VOLUMEGEWICHT_DRG', 'TXT_SS_WATERGEHALTE_VOOR'] + else: + columns_extra = ['DSS_VOLUMEGEWICHT_NAT', 'DSS_VOLUMEGEWICHT_DRG', 'DSS_WATERGEHALTE_VOOR'] + + # Check welke kolommen bestaan in de dataframe + available_columns = [col for col in columns_base + columns_extra if col in self.total_shansep_data_df.columns] + + if not available_columns: + return Table([['Geen invoerdata kolommen beschikbaar']], hAlign='LEFT') + + table_df = self.total_shansep_data_df[available_columns].copy() + + # Add additional columns from shansep_data_df_nc_oc if available and data is properly sorted + additional_columns = ['S\'v', 'Su', 'consolidatietype', 'OCR'] + if (hasattr(self, 'shansep_data_df_nc_oc_unsorted') and + self.shansep_data_df_nc_oc_unsorted is not None and + len(self.shansep_data_df_nc_oc_unsorted) == len(table_df)): + + # Check which additional columns are available + for col in additional_columns: + if col in self.shansep_data_df_nc_oc_unsorted.columns and col not in table_df.columns: + # Ensure the indices match before adding the column + if table_df.index.equals(self.shansep_data_df_nc_oc_unsorted.index): + table_df[col] = self.shansep_data_df_nc_oc_unsorted[col] + + else: + print(f"WAARSCHUWING: extra kolommen {additional_columns} in invoer tabel konden niet worden toegevoegd aan invoer tabel vanwege ontbrekende of niet-overeenkomende gegevens.") + + # Hernoem kolommen voor leesbaarheid met kortere namen en line breaks + column_mapping = { + 'PV_NAAM': 'Groep', + 'BORING_POSITIE': 'Pos.', + 'MONSTER_NIVEAU_NAP_VANAF': 'NAP Van\n[m]', + 'MONSTER_NIVEAU_NAP_TOT': 'NAP Tot\n[m]', + 'TXT_SS_VOLUMEGEWICHT_NAT': 'VGW nat\n[kN/m3]', + 'TXT_SS_VOLUMEGEWICHT_DRG': 'VGW drg\n[kN/m3]', + 'TXT_SS_WATERGEHALTE_VOOR': 'Water\n[%]', + 'DSS_VOLUMEGEWICHT_NAT': 'VGW nat\n[kN/m3]', + 'DSS_VOLUMEGEWICHT_DRG': 'VGW drg\n[kN/m3]', + 'DSS_WATERGEHALTE_VOOR': 'Water\n[%]', + 'S\'v': "σ'v\n[kPa]", + 'Su': "su\n[kPa]", + 'OCR': 'OCR\n[-]', + 'ANA_TERREINSPANNING': 'Terreinspanning\n[kPa]', + 'ANA_GRENSSPANNING_REKEN': 'Grensspanning\n[kPa]', + 'ANA_POP_VELD': 'POP\n[-]' + } + + table_df = table_df.rename(columns={k: v for k, v in column_mapping.items() if k in table_df.columns}) + + # replace column POP by new calculation to account for missing values: terreinspanning*ocr-terreinspanning + if 'Terreinspanning\n[kPa]' in table_df.columns and 'OCR\n[-]' in table_df.columns: + table_df['POP\n[-]'] = table_df['Terreinspanning\n[kPa]'] * table_df['OCR\n[-]'] - table_df['Terreinspanning\n[kPa]'] + + # Sort table_df in the same way as shansep_data_df_nc_oc + # Check if consolidatietype column exists in the data (use original column name before renaming) + + if 'consolidatietype' in table_df.columns: + table_df = table_df.sort_values( + by=['consolidatietype', table_df.index.name or table_df.index], + ascending=[False, True] + ).copy() + else: + table_df = table_df.sort_index() + print(f"WAARSCHUWING: invoer tabel kon niet worden gesorteerd op 'consolidatietype' omdat de kolom ontbreekt.") + + # delete consolidatietype column after sorting + if 'consolidatietype' in table_df.columns: + table_df = table_df.drop(columns=['consolidatietype']) + + # Handle NaN values and format numeric values properly + for col in table_df.columns: + if table_df[col].dtype in ['float64', 'int64']: + table_df[col] = table_df[col].apply( + lambda x: f"{x:.2f}" if isinstance(x, (float, int)) and not np.isnan(x) else "" + ) + else: + table_df[col] = table_df[col].apply( + lambda x: "" if (isinstance(x, float) and np.isnan(x)) or x is None else str(x) + ) + + # Create table data with proper line break handling + # First create the header row with Paragraph objects for consistent formatting + from reportlab.lib.styles import getSampleStyleSheet + header_style = getSampleStyleSheet()['Normal'] + header_style.fontSize = 7 # Set to 7 as requested + header_style.fontName = 'Helvetica-Bold' + header_style.alignment = 0 # Left align + + # Create header row with Paragraph objects for ALL headers (consistent formatting) + header_row = [] + header_row.append(Paragraph('Monster ID', header_style)) # Make Monster ID a Paragraph too + + for col in table_df.columns: + if '\n' in col: + # Create Paragraph for multi-line headers + header_row.append(Paragraph(col.replace('\n', '
'), header_style)) + else: + # Create Paragraph for single-line headers too (for consistency) + header_row.append(Paragraph(col, header_style)) + + # Create data rows + data_rows = [] + for idx, row in table_df.iterrows(): + data_row = [str(idx)] + [str(val) if val != "" else "" for val in row] + data_rows.append(data_row) + + # Combine header and data + t1_data = [header_row] + data_rows + + # Create table with automatic column sizing + t1 = LongTable(t1_data, repeatRows=1, hAlign='LEFT') + t1.setStyle(TableStyle([ + ('ALIGN', (0, 0), (-1, -1), 'LEFT'), + ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey), + ('GRID', (0, 0), (-1, -1), 0.5, colors.black), + ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'), # Only apply font to data rows (headers are Paragraphs) + ('FONTSIZE', (0, 1), (-1, -1), 6), # Set data font size to 6 as requested + ('VALIGN', (0, 0), (-1, 0), 'MIDDLE'), # Header row alignment + ('VALIGN', (0, 1), (-1, -1), 'TOP'), # Data row alignment + ('LEFTPADDING', (0, 0), (-1, -1), 2), # Reduce padding + ('RIGHTPADDING', (0, 0), (-1, -1), 2), + ('TOPPADDING', (0, 0), (-1, -1), 2), + ('BOTTOMPADDING', (0, 0), (-1, -1), 2), + # Special styling for header row with Paragraphs + ('ROWBACKGROUNDS', (0, 0), (-1, 0), [colors.lightgrey]), + ])) + return t1 + + + +def _create_parameters_table(self: "SHANSEP") -> Table: + """ + Maakt een tabel met de SHANSEP parameters. + + Returns + ------- + Table + ReportLab tabel object met de SHANSEP parameters + """ + parameters: List[list] = [] + + # Voeg parameters toe + if hasattr(self, 'e_a1_oc') and self.e_a1_oc is not None and self.snijpunt_gem_handmatig is None: + parameters.append(['Snijpunt y-as gemiddeld [kPa] (voorstel)', round(self.e_a1_oc, 3)]) + if hasattr(self, 'e_a2_oc') and self.e_a2_oc is not None and self.s_gem_handmatig is None: + parameters.append(['S gemiddeld [-] (voorstel)', round(self.e_a2_oc, 3)]) + if hasattr(self, 'e_a2_nc_oc') and self.e_a2_nc_oc is not None and self.m_gem_handmatig is None: + parameters.append(['m gemiddeld [-] (voorstel)', round(self.e_a2_nc_oc, 3)]) + if hasattr(self, 'pop_gem_oc') and self.pop_gem_oc is not None and self.pop_gem_handmatig is None: + parameters.append(['POP gemiddeld [kPa] (voorstel)', round(self.pop_gem_oc, 3)]) + if hasattr(self, 'a1_kar_oc') and self.a1_kar_oc is not None and self.snijpunt_kar_handmatig is None: + parameters.append(['Snijpunt y-as karakteristiek [kPa] (voorstel)', round(self.a1_kar_oc, 3)]) + if hasattr(self, 'a2_kar_oc') and self.a2_kar_oc is not None and self.s_kar_handmatig is None: + parameters.append(['S karakteristiek [-] (voorstel)', round(self.a2_kar_oc, 3)]) + if hasattr(self, 'a2_kar_nc_oc') and self.a2_kar_nc_oc is not None and self.m_kar_handmatig is None: + parameters.append(['m karakteristiek [-] (voorstel)', round(self.a2_kar_nc_oc, 3)]) + if hasattr(self, 'pop_kar_oc') and self.pop_kar_oc is not None and self.pop_kar_handmatig is None: + parameters.append(['POP karakteristiek [kPa] (voorstel)', round(self.pop_kar_oc, 3)]) + + parameters.append(['Type verzameling: lokaal = 1.0; regionaal = 0.75', self.alpha]) + + if hasattr(self, 'calc_vgwnat_gem') and self.calc_vgwnat_gem is not None: + parameters.append(['VGW nat gemiddeld [kN/m3]', round(self.calc_vgwnat_gem, 3)]) + if hasattr(self, 'calc_watergehalte_gem') and self.calc_watergehalte_gem is not None: + parameters.append(['Watergehalte gemiddeld', round(self.calc_watergehalte_gem, 3)]) + + t = Table([['Parameter', 'Waarde']] + parameters, hAlign='LEFT') + t.setStyle(TableStyle([ + ('ALIGN', (0, 0), (-1, -1), 'LEFT'), + ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey), + ('GRID', (0, 0), (-1, -1), 0.5, colors.black), + ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), + ('FONTSIZE', (0, 0), (-1, 0), 10), + ('FONTSIZE', (0, 1), (-1, -1), 9), + ])) + return t + + +def _create_gem_results_table(self: "SHANSEP") -> Table: + """ + Maakt een tabel met de SHANSEP gemiddelde resultaten (alleen voorgestelde parameters). + + Returns + ------- + Table + ReportLab tabel object met de SHANSEP gemiddelde resultaten + """ + df_gem, df_kar = self.get_result_values_shansep() + + # Filter out handmatige rows - alleen voorgestelde parameters behouden + df_gem_filtered = df_gem[~df_gem.index.str.contains('handmatig', na=False)] + + # Combineer gemiddelde resultaten + df_gem_new = DataFrame({ + 'Analyse methode': df_gem_filtered.index, + 'Snijpunt y-as [kPa]': df_gem_filtered['snijpunt y-as [kPa]'].values, + 'S [-]': df_gem_filtered['Schuifsterkteratio S [-]'].values, + 'm [-]': df_gem_filtered['sterkte toename exponent = m [-]'].values, + 'POP [kPa]': df_gem_filtered['POP [kPa]'].values + }) + + # Format numeric values and handle NaN properly + for col in df_gem_new.columns: + if col != 'Analyse methode': + df_gem_new[col] = df_gem_new[col].apply( + lambda x: f"{x:.3f}" if isinstance(x, (float, int)) and x is not None and not np.isnan(x) else "" + ) + + # Maak tabel data zonder index + header = df_gem_new.columns.tolist() + data = [row.tolist() for _, row in df_gem_new.iterrows()] + t_data = [header] + data + + t = LongTable(t_data, repeatRows=1, hAlign='LEFT') + t.setStyle(TableStyle([ + ('ALIGN', (0, 0), (-1, -1), 'LEFT'), + ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey), + ('GRID', (0, 0), (-1, -1), 0.5, colors.black), + ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), + ('FONTSIZE', (0, 0), (-1, 0), 10), + ('FONTSIZE', (0, 1), (-1, -1), 9), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ])) + return t + +def _create_kar_results_table(self: "SHANSEP") -> Table: + """ + Maakt een tabel met de SHANSEP karakteristieke resultaten (alleen voorgestelde parameters). + + Returns + ------- + Table + ReportLab tabel object met de SHANSEP karakteristieke resultaten + """ + df_gem, df_kar = self.get_result_values_shansep() + + # Filter out handmatige rows - alleen voorgestelde parameters behouden + df_kar_filtered = df_kar[~df_kar.index.str.contains('handmatig', na=False)] + + df_kar_new = DataFrame({ + 'Analyse methode': df_kar_filtered.index, + 'Snijpunt y-as [kPa]': df_kar_filtered['snijpunt y-as [kPa]'].values, + 'S [-]': df_kar_filtered['Schuifsterkteratio S [-]'].values, + 'm [-]': df_kar_filtered['sterkte toename exponent = m [-]'].values, + 'POP [kPa]': df_kar_filtered['POP [kPa]'].values, + }) + + # Format numeric values and handle NaN properly + for col in df_kar_new.columns: + if col != 'Analyse methode': + df_kar_new[col] = df_kar_new[col].apply( + lambda x: f"{x:.3f}" if isinstance(x, (float, int)) and x is not None and not np.isnan(x) else "" + ) + + # Maak tabel data zonder index + header = df_kar_new.columns.tolist() + data = [row.tolist() for _, row in df_kar_new.iterrows()] + t_data = [header] + data + + t = LongTable(t_data, repeatRows=1, hAlign='LEFT') + t.setStyle(TableStyle([ + ('ALIGN', (0, 0), (-1, -1), 'LEFT'), + ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey), + ('GRID', (0, 0), (-1, -1), 0.5, colors.black), + ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), + ('FONTSIZE', (0, 0), (-1, 0), 10), + ('FONTSIZE', (0, 1), (-1, -1), 9), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ])) + return t + +def _create_handmatig_results_table(self: "SHANSEP") -> Table: + """ + Maakt een tabel met de SHANSEP handmatige parameters in horizontale indeling. + + Returns + ------- + Table + ReportLab tabel object met de SHANSEP handmatige parameters + """ + # Verzamel gemiddelde handmatige waarden + gem_data = { + 'Snijpunt y-as [kPa]': round(self.snijpunt_gem_handmatig, 3) if self.snijpunt_gem_handmatig is not None else "", + 'S [-]': round(self.s_gem_handmatig, 3) if self.s_gem_handmatig is not None else "", + 'm [-]': round(self.m_gem_handmatig, 3) if self.m_gem_handmatig is not None else "", + 'POP [kPa]': round(self.pop_gem_handmatig, 3) if self.pop_gem_handmatig is not None else "" + } + + # Verzamel karakteristieke handmatige waarden + kar_data = { + 'Snijpunt y-as [kPa]': round(self.snijpunt_kar_handmatig, 3) if self.snijpunt_kar_handmatig is not None else "", + 'S [-]': round(self.s_kar_handmatig, 3) if self.s_kar_handmatig is not None else "", + 'm [-]': round(self.m_kar_handmatig, 3) if self.m_kar_handmatig is not None else "", + 'POP [kPa]': round(self.pop_kar_handmatig, 3) if self.pop_kar_handmatig is not None else "" + } + + # Maak DataFrame in hetzelfde formaat als gem/kar tabellen + df_handmatig = DataFrame({ + 'Analyse methode': ['Gemiddeld (handmatig)', 'Karakteristiek (handmatig)'], + 'Snijpunt y-as [kPa]': [gem_data['Snijpunt y-as [kPa]'], kar_data['Snijpunt y-as [kPa]']], + 'S [-]': [gem_data['S [-]'], kar_data['S [-]']], + 'm [-]': [gem_data['m [-]'], kar_data['m [-]']], + 'POP [kPa]': [gem_data['POP [kPa]'], kar_data['POP [kPa]']] + }) + + # Maak tabel data + header = df_handmatig.columns.tolist() + data = [row.tolist() for _, row in df_handmatig.iterrows()] + t_data = [header] + data + + t = LongTable(t_data, repeatRows=1, hAlign='LEFT') + t.setStyle(TableStyle([ + ('ALIGN', (0, 0), (-1, -1), 'LEFT'), + ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey), + ('GRID', (0, 0), (-1, -1), 0.5, colors.black), + ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), + ('FONTSIZE', (0, 0), (-1, 0), 10), + ('FONTSIZE', (0, 1), (-1, -1), 9), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ])) + return t + +def save_to_pdf(self: "SHANSEP", path: str) -> str: + """ + Slaat de SHANSEP analyseresultaten op in een PDF-document. + + De PDF bevat: + - Titel met analysedetails + - Beide overzichtsfiguren van de analyse (sv-su en ln(OCR)-ln(su/svc)) + - Su tabel + - Tabel met gemiddelde en karakteristieke resultaten + - Tabel met invoerselectie informatie + + Parameters + ---------- + path : str + Map locatie waar het PDF-bestand moet worden opgeslagen + + Returns + ------- + str + Het absolute bestandspad van het aangemaakte PDF-bestand + """ + # Maak titel en bestandsnaam + title = f"SHANSEP analyse met {self.effective_stress} op {self.investigation_groups[0]}" + file_name = f"shansep_pdf_export_{self.investigation_groups[0]}_{self.analysis_type}_{str(self.effective_stress).replace('%', 'procent_').replace(' ', '')}.pdf" + file_path = f"{path}/{file_name}" + + # Ensure analysis is run + self._run_shansep() + + # Maak het PDF document + doc = SimpleDocTemplate(file_path, pagesize=landscape(A4)) + styles = getSampleStyleSheet() + + # Voeg alleen styles toe die nog niet bestaan + if 'Left' not in styles: + styles.add(ParagraphStyle(name='Left', parent=styles['Normal'], alignment=TA_LEFT)) + if 'TitleLeft' not in styles: + styles.add(ParagraphStyle(name='TitleLeft', parent=styles['Title'], alignment=TA_LEFT)) + if 'Heading3' not in styles: + styles.add(ParagraphStyle(name='Heading3', parent=styles['Heading2'], alignment=TA_LEFT)) + story = [] + + # Voeg titel toe + story.append(Paragraph(title, styles['TitleLeft'])) + story.append(Spacer(width=1, height=12)) + + # Probeer figuren te genereren en toe te voegen + # Controleer of handmatige parameters zijn ingesteld + has_manual_params = hasattr(self, 'parameters_handmatig') and self.parameters_handmatig + + from reportlab.platypus import PageBreak, Image as RLImage + from PIL import Image as PILImage + + # Eerste figuur: sv-su plot + try: + fig_path1 = f"{path}/temp_plot1.png" + self.show_title = False + + # Genereer figuur + self.figure = go.Figure() + self.set_figure_sv_su() + + if hasattr(self, 'figure') and self.figure is not None: + fig_width = 1280 + fig_height = 720 + self.figure.write_image(fig_path1, width=fig_width, height=fig_height, scale=4, format="png") + + # Bereken figuurgrootte voor pagina 1 (met titel en heading) + # Ruimte voor titel (ongeveer 50 pt) + heading (ongeveer 25 pt) + spacers (ongeveer 25 pt) + title_and_heading_height = 100 + available_height = doc.height - title_and_heading_height + + # Laad PNG en bepaal pixelafmetingen + with PILImage.open(fig_path1) as im: + img_width_px, img_height_px = im.size + + # Bereken optimale grootte die op de pagina past + max_width_pt = doc.width * 0.95 + aspect = img_height_px / img_width_px + + # Probeer eerst op basis van breedte + img_width_pt = min(max_width_pt, doc.width) + img_height_pt = img_width_pt * aspect + + # Controleer of het past in de beschikbare hoogte, anders schaal naar hoogte + if img_height_pt > available_height: + img_height_pt = available_height * 0.95 + img_width_pt = img_height_pt / aspect + + # Maak ReportLab Image aan + img1 = RLImage(fig_path1) + img1.drawWidth = img_width_pt + img1.drawHeight = img_height_pt + img1.hAlign = 'LEFT' + + # Voeg titel toe die aangeeft of handmatige parameters zijn gebruikt + if has_manual_params: + story.append(Paragraph("su - σ'v Relatie (met handmatige parameters)", styles['Heading3'])) + else: + story.append(Paragraph("su - σ'v Relatie (met voorgestelde parameters)", styles['Heading3'])) + story.append(Spacer(width=1, height=12)) + story.append(img1) + + # Nieuwe pagina voor tweede figuur + story.append(PageBreak()) + + except Exception as e: + print(f"Waarschuwing: Kon eerste figuur (Sv-Su) niet toevoegen aan PDF: {e}") + story.append(Paragraph("Eerste figuur: (kon niet worden gegenereerd)", styles['Heading3'])) + story.append(Spacer(width=1, height=12)) + story.append(PageBreak()) + + # Tweede figuur: ln(OCR) - ln(su/svc) plot + try: + fig_path2 = f"{path}/temp_plot2.png" + self.show_title = False + + # Genereer figuur + self.figure = go.Figure() + self.set_figure_ln_ocr_ln_s() + + if hasattr(self, 'figure') and self.figure is not None: + fig_width = 1280 + fig_height = 720 + self.figure.write_image(fig_path2, width=fig_width, height=fig_height, scale=4, format="png") + + # Bereken figuurgrootte voor pagina 2 (alleen met heading) + # Ruimte voor heading (ongeveer 25 pt) + spacer (ongeveer 12 pt) + heading_height = 37 + available_height = doc.height - heading_height + + # Laad PNG en bepaal pixelafmetingen + with PILImage.open(fig_path2) as im: + img_width_px, img_height_px = im.size + + # Bereken optimale grootte die op de pagina past + max_width_pt = doc.width * 0.95 + aspect = img_height_px / img_width_px + + # Probeer eerst op basis van breedte + img_width_pt = min(max_width_pt, doc.width) + img_height_pt = img_width_pt * aspect + + # Controleer of het past in de beschikbare hoogte, anders schaal naar hoogte + if img_height_pt > available_height: + img_height_pt = available_height * 0.95 + img_width_pt = img_height_pt / aspect + + # Maak ReportLab Image aan + img2 = RLImage(fig_path2) + img2.drawWidth = img_width_pt + img2.drawHeight = img_height_pt + img2.hAlign = 'LEFT' + + # Voeg titel toe die aangeeft of handmatige parameters zijn gebruikt + if has_manual_params: + story.append(Paragraph("LN(OCR) - LN(su/σ'v) Relatie (met handmatige parameters)", styles['Heading3'])) + else: + story.append(Paragraph("LN(OCR) - LN(su/σ'v) Relatie (met voorgestelde parameters)", styles['Heading3'])) + story.append(Spacer(width=1, height=12)) + story.append(img2) + + # Nieuwe pagina voor derde figuur + story.append(PageBreak()) + + except Exception as e: + print(f"Waarschuwing: Kon tweede figuur (ln(OCR) - ln(su/svc)) niet toevoegen aan PDF: {e}") + story.append(Paragraph("Tweede figuur: (kon niet worden gegenereerd)", styles['Heading3'])) + story.append(Spacer(width=1, height=12)) + story.append(PageBreak()) + + # Derde figuur: sv-su NC plot + try: + fig_path3 = f"{path}/temp_plot3.png" + self.show_title = False + + self.figure = go.Figure() + self.set_figure_sv_su_nc() + + if hasattr(self, 'figure') and self.figure is not None: + fig_width = 1280 + fig_height = 720 + self.figure.write_image(fig_path3, width=fig_width, height=fig_height, scale=4, format="png") + + # Bereken figuurgrootte voor pagina 3 (alleen met heading) + # Ruimte voor heading (ongeveer 25 pt) + spacer (ongeveer 12 pt) + heading_height = 37 + available_height = doc.height - heading_height + + # Laad PNG en bepaal pixelafmetingen + with PILImage.open(fig_path3) as im: + img_width_px, img_height_px = im.size + + # Bereken optimale grootte die op de pagina past + max_width_pt = doc.width * 0.95 + aspect = img_height_px / img_width_px + + # Probeer eerst op basis van breedte + img_width_pt = min(max_width_pt, doc.width) + img_height_pt = img_width_pt * aspect + + # Controleer of het past in de beschikbare hoogte, anders schaal naar hoogte + if img_height_pt > available_height: + img_height_pt = available_height * 0.95 + img_width_pt = img_height_pt / aspect + + # Maak ReportLab Image aan + img3 = RLImage(fig_path3) + img3.drawWidth = img_width_pt + img3.drawHeight = img_height_pt + img3.hAlign = 'LEFT' + + # Voeg titel toe die aangeeft of handmatige parameters zijn gebruikt + if has_manual_params: + story.append(Paragraph("su - σ'v Relatie op alleen NC proeven (met handmatige parameters)", styles['Heading3'])) + else: + story.append(Paragraph("su - σ'v Relatie op alleen NC proeven (met voorgestelde parameters)", styles['Heading3'])) + story.append(Spacer(width=1, height=12)) + story.append(img3) + + # Nieuwe pagina voor de tabellen en resultaten + story.append(PageBreak()) + + except Exception as e: + print(f"Waarschuwing: Kon derde figuur (Sv-Su NC) niet toevoegen aan PDF: {e}") + story.append(Paragraph("Derde figuur: (kon niet worden gegenereerd)", styles['Heading3'])) + story.append(Spacer(width=1, height=12)) + story.append(PageBreak()) + + # Reset title setting + self.show_title = True + + # Voeg parameters toe + story.append(Paragraph("Invoer parameters en eigenschappen", styles['Heading2'])) + story.append(_create_parameters_table(self)) + story.append(Spacer(1, 12)) + + # Voeg resultaten toe + story.append(Paragraph("SHANSEP Resultaten eerste benadering gemiddeld", styles['Heading2'])) + story.append(_create_gem_results_table(self)) + story.append(Spacer(1, 12)) + + # Voeg resultaten toe + story.append(Paragraph("SHANSEP Resultaten eerste benadering karakteristiek", styles['Heading2'])) + story.append(_create_kar_results_table(self)) + story.append(Spacer(1, 12)) + + story.append(PageBreak()) + + # Voeg handmatige resultaten toe (alleen als er handmatige parameters zijn) + if any([ + self.snijpunt_gem_handmatig is not None, + self.s_gem_handmatig is not None, + self.m_gem_handmatig is not None, + self.pop_gem_handmatig is not None, + self.snijpunt_kar_handmatig is not None, + self.s_kar_handmatig is not None, + self.m_kar_handmatig is not None, + self.pop_kar_handmatig is not None + ]): + story.append(Paragraph("SHANSEP Handmatige parameters", styles['Heading2'])) + story.append(_create_handmatig_results_table(self)) + story.append(Spacer(1, 12)) + + + # Voeg Su tabel toe indien beschikbaar + if hasattr(self, 'sutabel') and self.sutabel is not None: + story.append(Paragraph("Su Tabel", styles['Heading2'])) + + # Handle NaN values in the Su table + sutabel_clean = self.sutabel.copy() + + # Replace NaN values with empty strings for all columns + for col in sutabel_clean.columns: + sutabel_clean[col] = sutabel_clean[col].apply( + lambda x: "" if (isinstance(x, float) and np.isnan(x)) or x is None else x + ) + sutabel_clean[col] = sutabel_clean[col].map(lambda x: f"{x:.3f}" if isinstance(x, (float, int)) else x) + + # Maak tabel data zonder index + header = sutabel_clean.columns.tolist() + data = [row.tolist() for _, row in sutabel_clean.iterrows()] + sutabel_data = [header] + data + + sutabel_table = LongTable(sutabel_data, repeatRows=1, hAlign='LEFT') + sutabel_table.setStyle(TableStyle([ + ('ALIGN', (0, 0), (-1, -1), 'LEFT'), + ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey), + ('GRID', (0, 0), (-1, -1), 0.5, colors.black), + ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), + ('FONTSIZE', (0, 0), (-1, 0), 10), + ('FONTSIZE', (0, 1), (-1, -1), 9), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ])) + story.append(sutabel_table) + story.append(Spacer(1, 12)) + + # Voeg invoerselectie toe + story.append(Paragraph("Invoerselectie Informatie", styles['Heading2'])) + story.append(_create_input_table(self)) + + # Maak PDF + doc.build(story) + print(f"SHANSEP PDF export voltooid: {file_path}") + + return file_path diff --git a/pv_tool/shansep_analysis/shansep_analysis.py b/pv_tool/shansep_analysis/shansep_analysis.py index ea43a33..261059b 100644 --- a/pv_tool/shansep_analysis/shansep_analysis.py +++ b/pv_tool/shansep_analysis/shansep_analysis.py @@ -1,28 +1,74 @@ +import math from pv_tool.imports.import_data import Dbase from typing import Optional, List, Literal from pv_tool.shansep_analysis.globals import (TEXTUAL_NAMES, NEW_COLUMN_NAMES, TEXTUAL_NAMES_DSS) -from pandas import DataFrame, ExcelWriter, concat, read_excel +from pandas import DataFrame, ExcelWriter, read_excel +import plotly.graph_objects as go +from pv_tool.shansep_analysis.calc_parameters import ( + calc_watergehalte_gem_txt, calc_watergehalte_gem_dss, + calc_watergehalte_sd_txt, calc_watergehalte_sd_dss, + calc_vgwnat_gem_txt, calc_vgwnat_gem_dss, + calc_vgwnat_sd_txt, calc_vgwnat_sd_dss +) +from pv_tool.imports.excel_utils import format_excel_sheet -from pv_tool.shansep_analysis.calc_parameters import (calc_watergehalte_gem, calc_watergehalte_sd, calc_vgwnat_gem, - calc_vgwnat_sd) +from pv_tool.shansep_analysis.visualization_shansep import ( + add_proefresultaten_sv_su, + add_extra_proefresultaten_sv_su, + add_extra_proefresultaten_sv_su_nc, + add_extra_proefresultaten_ln_ocr_ln_s, + add_5pr_bovengrens_sv_su, + add_5pr_ondergrens_sv_su, + add_fysische_realiseerbare_ondergrens_sv_su, + add_lineair_fit_sv_su, + add_proefresultaten_ln_ocr_ln_s, + add_5pr_bovengrens_ln_ocr_ln_s, + add_5pr_ondergrens_ln_ocr_ln_s, + add_lineair_fit_ln_ocr_ln_s, + set_layout_sv_su, + set_layout_ln_ocr_ln_s, add_shansep_lijn_sv_su, add_shansep_lijn_ln_ocr_ln_s, + add_proefresultaten_sv_su_nc, add_fysische_realiseerbare_ondergrens_sv_su_nc, + add_lineair_fit_sv_su_nc, add_shansep_lijn_sv_su_nc +) + +from pv_tool.shansep_analysis.variables import ( + exp_gem_ln_su_svc_nc, gem_pop_oc, exp_kar_ln_su_svc_nc, kar_pop_oc, + e_a1_oc, e_a2_oc, e_a1_nc_oc, e_a2_nc_oc, + a1_kar_oc, a2_kar_oc, + a1_kar_nc_oc, a2_kar_nc_oc, + st_dev_s_handmatig, st_dev_pop_handmatig, st_dev_m_handmatig +) + +from pv_tool.shansep_analysis.expand_analysis import (calculate_ln_ocr, calculate_pop, + calculate_chi_2_nc_oc, calculate_sv_tt_oc, calculate_sv_spop, + calculate_chi_2_oc, calculate_ln_sv_spop, calculate_sv_ty_oc, + calculate_chi_2_ondergrens_nc_oc, calculate_sv_tt_ondergrens_nc_oc, + calculate_chi_2_ondergrens_oc, calculate_sv_tt_nc_oc, + calculate_sv_ty_ondergrens_nc_oc, calculate_sv_ty_ondergrens_oc, + calculate_5pr_ondergrens_nc_oc, calculate_5pr_ondergrens_oc, + calculate_sv_ty_nc_oc, calculate_sv_tt_ondergrens_oc, calculate_sv_eff_oc, + calculate_sv_eff_nc_oc, calculate_5pr_bovengrens_oc, calculate_5pr_bovengrens_nc_oc) + +from pv_tool.shansep_analysis.save_and_export import ( + add_results_to_dbase as _add_results_to_dbase, + save_total_to_excel as _save_total_to_excel, + save_to_pdf as _save_to_pdf +) + + +#-------------------------- SHANSEP Analysis Class ---------------------------------------# -from pv_tool.shansep_analysis.expand_analysis import (calculate_s_tt, calculate_s_sutabel, calculate_pop, - calculate_s_spop, calculate_ln_ocr, calculate_ln_s_spop, - calculate_s_ty, calculate_kappa_2, - calculate_5pr_ondergrens, calculate_5pr_bovengrens, - calculate_s_tt_ondergrens, calculate_s_ty_ondergrens, - calculate_kappa_2_ondergrens) class SHANSEP: def __init__(self, dbase: Dbase, - analysis_type: Literal['TXT_S_POP', 'TXT_su_tabel', 'DSS_S_POP', 'DSS_su_tabel'], + analysis_type: Literal['TXT_S_POP', 'DSS_S_POP'], investigation_groups: List, effective_stress: Literal['2% rek', '5% rek', '10% rek', '15% rek', '20% rek', 'pieksterkte', 'eindsterkte']): # Validate effective_stress based on analysis_type - if analysis_type in ['TXT_S_POP', 'TXT_su_tabel'] and effective_stress in ['10% rek', '20% rek']: + if analysis_type in ['TXT_S_POP'] and effective_stress in ['10% rek', '20% rek']: raise ValueError( f"De waardes '10% rek' and '20% rek' kunnen alleen gebruikt worden bij de DSS analyse. " f"De gekozen analyse is: '{analysis_type}', en de sterkte is: '{effective_stress}'" @@ -38,101 +84,585 @@ def __init__(self, dbase: Dbase, self.alpha: Optional[float] = 0.75 # Parameters - self.calc_watergehalte_gem: Optional[float] = None self.calc_watergehalte_sd: Optional[float] = None self.calc_vgwnat_gem: Optional[float] = None self.calc_vgwnat_sd: Optional[float] = None - # Placeholder + self.e_a2_oc: Optional[float] = None + self.e_a1_oc: Optional[float] = None + self.e_a2_nc_oc: Optional[float] = None + self.e_a1_nc_oc: Optional[float] = None + self.exp_e_a1_nc_oc: Optional[float] = None + self.exp_gem_ln_su_svc_nc: Optional[float] = None + self.pop_gem_oc: Optional[float] = None + + self.a2_kar_oc: Optional[float] = None + self.a1_kar_oc: Optional[float] = None + self.a2_kar_nc_oc: Optional[float] = None + self.a1_kar_nc_oc: Optional[float] = None + self.exp_a1_kar_nc_oc: Optional[float] = None + self.exp_kar_ln_su_svc_nc: Optional[float] = None + self.pop_kar_oc: Optional[float] = None + + + # Handmatige parameters + self.parameters_handmatig: Optional[bool] = False + + self.snijpunt_gem_handmatig: Optional[float] = None + self.s_gem_handmatig: Optional[float] = None + self.m_gem_handmatig: Optional[float] = None + + self.snijpunt_kar_handmatig: Optional[float] = None + self.s_kar_handmatig: Optional[float] = None + self.m_kar_handmatig: Optional[float] = None + + self.pop_kar_handmatig: Optional[float] = None + self.pop_gem_handmatig: Optional[float] = None + + self.st_dev_s_handmatig: Optional[float] = None + self.st_dev_pop_handmatig: Optional[float] = None + self.st_dev_m_handmatig: Optional[float] = None + + # dataframes self.shansep_data_df: Optional[DataFrame] = None self.total_shansep_data_df: Optional[DataFrame] = None + self.shansep_data_df_nc: Optional[DataFrame] = None self.shansep_data_df_oc: Optional[DataFrame] = None self.shansep_data_df_nc_oc: Optional[DataFrame] = None + self.shansep_data_df_nc_oc_unsorted: Optional[DataFrame] = None + self.sutabel: Optional[DataFrame] = None + self.sutabel_nc: Optional[DataFrame] = None + self.df_results_shansep_gem: Optional[DataFrame] = None + self.df_results_shansep_kar: Optional[DataFrame] = None + + # Figure + self.figure = go.Figure() + self.show_title: Optional[bool] = True - pass + # ========= Instelling en Data Ophalen Methodes ========== def get_shansep_data(self): """Deze functie filtert de dbase-dataframe op basis van type analyse, PV_NAAM en de gewenste effectieve stress. Daarnaast worden de kolomnamen aangepast zodat ze ongeacht het rekpercentage allemaal dezelfde kolomnaam hebben. """ - if self.analysis_type in ['TXT_S_POP', 'TXT_su_tabel']: - self.shansep_data_df = self.dbase_df[self.dbase_df['ALG__TRIAXIAAL']] + if self.analysis_type in ['TXT_S_POP']: + self.shansep_data_df = self.dbase_df[self.dbase_df['ALG__TRIAXIAAL']].copy() self.shansep_data_df = self.shansep_data_df[self.shansep_data_df['PV_NAAM'].isin( - self.investigation_groups)] - self.calc_watergehalte_gem = calc_watergehalte_gem(self) - self.calc_watergehalte_sd = calc_watergehalte_sd(self) - self.calc_vgwnat_gem = calc_vgwnat_gem(self) - self.calc_vgwnat_sd = calc_vgwnat_sd(self) - self.total_shansep_data_df = self.shansep_data_df - self.shansep_data_df = self.shansep_data_df[TEXTUAL_NAMES.get(self.effective_stress, [])] - - elif self.analysis_type in ['DSS_S_POP', 'DSS_su_tabel']: - self.shansep_data_df = self.dbase_df[self.dbase_df['ALG__DSS']] + self.investigation_groups)].copy() + self.calc_watergehalte_gem = calc_watergehalte_gem_txt(self.shansep_data_df) + self.calc_watergehalte_sd = calc_watergehalte_sd_txt(self.shansep_data_df) + self.calc_vgwnat_gem = calc_vgwnat_gem_txt(self.shansep_data_df) + self.calc_vgwnat_sd = calc_vgwnat_sd_txt(self.shansep_data_df) + self.total_shansep_data_df = self.shansep_data_df.copy() + self.shansep_data_df = self.shansep_data_df[TEXTUAL_NAMES.get(self.effective_stress, [])].copy() + + elif self.analysis_type in ['DSS_S_POP']: + self.shansep_data_df = self.dbase_df[self.dbase_df['ALG__DSS']].copy() self.shansep_data_df = self.shansep_data_df[self.shansep_data_df['PV_NAAM'].isin( - self.investigation_groups)] - self.calc_watergehalte_gem = calc_watergehalte_gem(self) - self.calc_watergehalte_sd = calc_watergehalte_sd(self) - self.calc_vgwnat_gem = calc_vgwnat_gem(self) - self.calc_vgwnat_sd = calc_vgwnat_sd(self) - self.total_shansep_data_df = self.shansep_data_df - self.shansep_data_df = self.shansep_data_df[TEXTUAL_NAMES_DSS.get(self.effective_stress, [])] + self.investigation_groups)].copy() + self.calc_watergehalte_gem = calc_watergehalte_gem_dss(self.shansep_data_df) + self.calc_watergehalte_sd = calc_watergehalte_sd_dss(self.shansep_data_df) + self.calc_vgwnat_gem = calc_vgwnat_gem_dss(self.shansep_data_df) + self.calc_vgwnat_sd = calc_vgwnat_sd_dss(self.shansep_data_df) + self.total_shansep_data_df = self.shansep_data_df.copy() + self.shansep_data_df = self.shansep_data_df[TEXTUAL_NAMES_DSS.get(self.effective_stress, [])].copy() self.shansep_data_df.columns = NEW_COLUMN_NAMES - - def apply_settings(self, alpha: Optional[float] = None): """Met deze functie kan je de alpha en materiaalfactoren opgeven.""" self.alpha = alpha if alpha is not None else self.alpha - def expand_analysis_df_sutabel(self): - """Deze functie berekent alle benodigde parameters per monster voor de analyse.""" - # calculate_s_tt(self) - # calculate_s_ty(self) - # calculate_kappa_2(self) - # calculate_s_sutabel(self) - # calculate_5pr_ondergrens(self) - # calculate_5pr_bovengrens(self) - # calculate_s_tt_ondergrens(self) - # calculate_s_ty_ondergrens(self) - # calculate_kappa_2_ondergrens(self) - return f"Deze functie is nog niet geïmplementeerd voor de su tabel analyse." + def get_previous_results(self, path: str, file_name: str): + """ + Zoekt naar eerdere analyseresultaten in een Excel-bestand. + + Parameters + ---------- + path: str + Pad naar de map waar het Excel-bestand staat + file_name: str + Naam van het Excel-bestand + + Returns + ------- + DataFrame of None + DataFrame met eerdere resultaten als deze gevonden zijn, anders None + """ + + file_path = f"{path}/{file_name}" + + try: + with open(file_path, 'r'): + pass + except FileNotFoundError: + raise FileNotFoundError(f"Er is geen dbase aanwezig op de locatie {file_path}.") + + try: + results_df = read_excel(file_path, sheet_name='Resultaten') + except ValueError: + print("Er is geen tabblad 'Resultaten' aanwezig in het Excel-bestand.") + return None + + filtered_df = results_df[ + (results_df['PV_RESULTAAT_ID'].str.contains(self.investigation_groups[0])) & + (results_df['PV_RESULTAAT_ID'].str.contains(self.effective_stress)) & + (results_df['PV_RESULTAAT_ID'].str.contains(self.analysis_type)) + ] + + if filtered_df.empty: + print("Er zijn geen eerdere resultaten gevonden voor de opgegeven parameters.") + return None + + latest_entry = filtered_df.sort_values(by='Timestamp', ascending=False).iloc[0] + + return latest_entry + + # ========= Analyse Methodes ========== def expand_analysis_df_s_pop_alleen_oc(self): """Deze functie berekent alle benodigde parameters per monster voor de analyse.""" calculate_ln_ocr(self) - calculate_s_spop(self) - calculate_ln_s_spop(self) + calculate_sv_spop(self) + calculate_ln_sv_spop(self) calculate_pop(self) self.shansep_data_df_oc = self.shansep_data_df[self.shansep_data_df['consolidatietype'] == 'OC'].copy() - calculate_s_tt(self) - calculate_s_ty(self) - calculate_kappa_2(self) - calculate_s_sutabel(self) - calculate_5pr_ondergrens(self) - calculate_5pr_bovengrens(self) - calculate_s_tt_ondergrens(self) - calculate_s_ty_ondergrens(self) - calculate_kappa_2_ondergrens(self) + calculate_sv_tt_oc(self) + calculate_sv_ty_oc(self) + calculate_chi_2_oc(self) + calculate_sv_eff_oc(self) + calculate_5pr_ondergrens_oc(self) + calculate_5pr_bovengrens_oc(self) + calculate_sv_tt_ondergrens_oc(self) + calculate_sv_ty_ondergrens_oc(self) + calculate_chi_2_ondergrens_oc(self) def expand_analysis_df_s_pop(self): """Deze functie berekent alle benodigde parameters per monster voor de analyse.""" calculate_ln_ocr(self) - calculate_s_spop(self) - calculate_ln_s_spop(self) + calculate_sv_spop(self) + calculate_ln_sv_spop(self) calculate_pop(self) self.shansep_data_df_nc_oc = self.shansep_data_df.copy() + self.shansep_data_df_nc_oc_unsorted = self.shansep_data_df.copy() + self.shansep_data_df_nc_oc = self.shansep_data_df_nc_oc.sort_values( + by=['consolidatietype', self.shansep_data_df_nc_oc.index.name or self.shansep_data_df_nc_oc.index], + ascending=[False, True] + ).copy() + + calculate_sv_tt_nc_oc(self) + calculate_sv_ty_nc_oc(self) + calculate_chi_2_nc_oc(self) + calculate_sv_eff_nc_oc(self) + calculate_5pr_ondergrens_nc_oc(self) + calculate_5pr_bovengrens_nc_oc(self) + calculate_sv_tt_ondergrens_nc_oc(self) + calculate_sv_ty_ondergrens_nc_oc(self) + calculate_chi_2_ondergrens_nc_oc(self) + + def get_shansep_parameters(self): + """ + Berekent de parameters van de shansep analyse + """ + + # Voor S-POP analyse berekenen we de standaard parameters + self.e_a2_oc = e_a2_oc(self) + self.e_a1_oc = e_a1_oc(self) + self.e_a2_nc_oc = e_a2_nc_oc(self) + self.e_a1_nc_oc = e_a1_nc_oc(self) + self.exp_e_a1_nc_oc = math.exp(e_a1_nc_oc(self)) + self.exp_gem_ln_su_svc_nc = exp_gem_ln_su_svc_nc(self) + self.pop_gem_oc = gem_pop_oc(self) + + self.a2_kar_oc = a2_kar_oc(self) + self.a1_kar_oc = a1_kar_oc(self) + self.a2_kar_nc_oc = a2_kar_nc_oc(self) + self.a1_kar_nc_oc = a1_kar_nc_oc(self) + self.exp_a1_kar_nc_oc = math.exp(a1_kar_nc_oc(self)) + self.exp_kar_ln_su_svc_nc = exp_kar_ln_su_svc_nc(self) + self.pop_kar_oc = kar_pop_oc(self) + + # Only calculate standard deviations if manual parameters are set + if hasattr(self, 'parameters_handmatig') and self.parameters_handmatig: + self.st_dev_m_handmatig = st_dev_m_handmatig(self) + self.st_dev_pop_handmatig = st_dev_pop_handmatig(self) + self.st_dev_s_handmatig = st_dev_s_handmatig(self) + else: + # Initialize as None when manual parameters are not set + self.st_dev_m_handmatig = None + self.st_dev_pop_handmatig = None + self.st_dev_s_handmatig = None + + def _run_shansep(self): + """ + Voert de volledige shansep analyse uit in de juiste volgorde: + data ophalen, parameters berekenen en resultaten bepalen. + """ + self.get_shansep_data() + + if self.analysis_type in ['TXT_S_POP', 'DSS_S_POP']: + self.expand_analysis_df_s_pop_alleen_oc() + self.expand_analysis_df_s_pop() + + self.get_shansep_parameters() + + # ========= Resultaten Methodes ========== + + def get_result_values_shansep(self): + """ + Berekent de definitieve resultaten van de shansep analyse + """ + self._run_shansep() + self.df_results_shansep_gem = DataFrame( + index=['bepaling S en POP uit triaxiaal- of DSS proeven', 'bepaling S en m uit triaxiaal- of DSS proeven', + 'o.b.v. opgegeven POP bij triaxiaal- of DSS proeven', + 'bepaling S uit triaxiaal- of DSS proeven OCR=1', + 'gemiddelde handmatige keuze snijput, S en m']) + self.df_results_shansep_gem['snijpunt y-as [kPa]'] = [self.e_a1_oc, None, None, None, self.snijpunt_gem_handmatig] + self.df_results_shansep_gem['Schuifsterkteratio S [-]'] = [self.e_a2_oc, self.exp_e_a1_nc_oc, None, self.exp_gem_ln_su_svc_nc, self.s_gem_handmatig] + self.df_results_shansep_gem['sterkte toename exponent = m [-]'] = [None, self.e_a2_nc_oc, None, None, self.m_gem_handmatig] + pop_bepaald = self.e_a1_oc/self.e_a2_oc/self.e_a2_nc_oc + self.df_results_shansep_gem['POP [kPa]'] = [pop_bepaald, pop_bepaald, self.pop_gem_oc, None, self.pop_gem_handmatig] + + self.df_results_shansep_kar = DataFrame( + index=['bepaling S en POP uit triaxiaal- of DSS proeven', 'bepaling S en m uit triaxiaal- of DSS proeven', + 'o.b.v. opgegeven POP bij triaxiaal- of DSS proeven', + 'bepaling S uit triaxiaal- of DSS proeven OCR=1', + 'karakteristieke handmatige keuze snijput, S en m']) + self.df_results_shansep_kar['snijpunt y-as [kPa]'] = [self.a1_kar_oc, None, None, None, self.snijpunt_kar_handmatig] + self.df_results_shansep_kar['Schuifsterkteratio S [-]'] = [self.a2_kar_oc, self.exp_a1_kar_nc_oc, None, self.exp_kar_ln_su_svc_nc, self.s_kar_handmatig] + self.df_results_shansep_kar['sterkte toename exponent = m [-]'] = [None, self.a2_kar_nc_oc, None, None, self.m_kar_handmatig] + pop_bepaald = self.a1_kar_oc/self.a2_kar_oc/self.a2_kar_nc_oc + self.df_results_shansep_kar['POP [kPa]'] = [pop_bepaald, pop_bepaald, self.pop_kar_oc, None, self.pop_kar_handmatig] + + # write these dataframes to excel + return self.df_results_shansep_gem, self.df_results_shansep_kar + + def get_result_values_nc(self): + """ + Berekent de resultaten voor NC (Normal Consolidated) analyse waarbij snijpunt, m en pop waarden op 0 staan. + Dit is bedoeld voor visualisatie van alleen NC data zonder overconsolidatie effecten. + + Returns + ------- + tuple[DataFrame, DataFrame] + Gemiddelde en karakteristieke resultaten voor NC analyse + """ + self._run_shansep() + + # Maak kopie van bestaande resultaten maar zet specifieke waarden op 0 voor NC analyse + df_results_nc_gem = DataFrame( + index=['bepaling S en POP uit triaxiaal- of DSS proeven', 'bepaling S en m uit triaxiaal- of DSS proeven', + 'o.b.v. opgegeven POP bij triaxiaal- of DSS proeven', + 'bepaling S uit triaxiaal- of DSS proeven OCR=1', + 'gemiddelde handmatige keuze snijput, S en m']) + + # Voor NC analyse: snijpunt = 0, m = 0, POP = 0 + df_results_nc_gem['snijpunt y-as [kPa]'] = [0, None, None, None, 0] + df_results_nc_gem['Schuifsterkteratio S [-]'] = [self.e_a2_oc, self.exp_e_a1_nc_oc, None, self.exp_gem_ln_su_svc_nc, self.s_gem_handmatig if self.s_gem_handmatig else 0] + df_results_nc_gem['sterkte toename exponent = m [-]'] = [None, 0, None, None, 0] + df_results_nc_gem['POP [kPa]'] = [0, 0, 0, None, 0] + + df_results_nc_kar = DataFrame( + index=['bepaling S en POP uit triaxiaal- of DSS proeven', 'bepaling S en m uit triaxiaal- of DSS proeven', + 'o.b.v. opgegeven POP bij triaxiaal- of DSS proeven', + 'bepaling S uit triaxiaal- of DSS proeven OCR=1', + 'karakteristieke handmatige keuze snijput, S en m']) + + # Voor NC analyse: snijpunt = 0, m = 0, POP = 0 + df_results_nc_kar['snijpunt y-as [kPa]'] = [0, None, None, None, 0] + df_results_nc_kar['Schuifsterkteratio S [-]'] = [self.a2_kar_oc, self.exp_a1_kar_nc_oc, None, self.exp_kar_ln_su_svc_nc, self.s_kar_handmatig if self.s_kar_handmatig else 0] + df_results_nc_kar['sterkte toename exponent = m [-]'] = [None, 0, None, None, 0] + df_results_nc_kar['POP [kPa]'] = [0, 0, 0, None, 0] + + return df_results_nc_gem, df_results_nc_kar + + def calculate_sutabel(self): + """print de blauwe tabel in de excel naar excel + wordt ook weggeschreven naar pdf bij save_to_pdf + wordt nu bij visualisation ook aangeroepen all + """ + self.sutabel = DataFrame(columns=['S\'v [kPa]', 'Su in-situ karakteristiek', 'Su in-situ gemiddeld']) + x = [0.1, 1, 5, 10, 20, 30, self.shansep_data_df_oc['S\'v'].max()] + shansep_kar = [self.s_kar_handmatig * x * ((self.pop_kar_handmatig + x) / x) ** self.m_kar_handmatig for x in x] + shansep_gem = [self.s_gem_handmatig * x * ((self.pop_gem_handmatig + x) / x) ** self.m_gem_handmatig for x in x] + + self.sutabel['S\'v [kPa]'] = x + self.sutabel['Su in-situ karakteristiek'] = shansep_kar + self.sutabel['Su in-situ gemiddeld'] = shansep_gem + return self.sutabel + + def calculate_sutabel_nc(self): + """print de blauwe tabel in de excel naar excel + wordt ook weggeschreven naar pdf bij save_to_pdf + wordt nu bij visualisation ook aangeroepen all + """ + self.sutabel_nc = DataFrame(columns=['S\'v [kPa]', 'Su in-situ karakteristiek', 'Su in-situ gemiddeld']) + x = [0.1, 1, 5, 10, 20, 30, self.shansep_data_df_nc['S\'v'].max()] + shansep_kar = [self.s_kar_handmatig * x * ((self.pop_kar_handmatig + x) / x) ** self.m_kar_handmatig for x in x] + shansep_gem = [self.s_gem_handmatig * x * ((self.pop_gem_handmatig + x) / x) ** self.m_gem_handmatig for x in x] + + self.sutabel_nc['S\'v [kPa]'] = x + self.sutabel_nc['Su in-situ karakteristiek'] = shansep_kar + self.sutabel_nc['Su in-situ gemiddeld'] = shansep_gem + return self.sutabel_nc + + def export_shansep_results_excel(self, file_path: str): + """ + Exporteert de shansep resultaten naar een Excel-bestand. - calculate_s_tt(self) - calculate_s_ty(self) - calculate_kappa_2(self) - calculate_s_sutabel(self) - calculate_5pr_ondergrens(self) - calculate_5pr_bovengrens(self) - calculate_s_tt_ondergrens(self) - calculate_s_ty_ondergrens(self) - calculate_kappa_2_ondergrens(self) \ No newline at end of file + Parameters + ---------- + file_path : str + Pad naar de map waar het Excel-bestand moet worden opgeslagen plus de bestandsnaam + """ + df_gem, df_kar = self.get_result_values_shansep() + + # Prepare DataFrames for Excel export to prevent corruption + # Ensure index is strings and has a name + df_gem.index = df_gem.index.astype(str) + df_kar.index = df_kar.index.astype(str) + if df_gem.index.name is None: + df_gem.index.name = 'Analyse' + if df_kar.index.name is None: + df_kar.index.name = 'Analyse' + + with ExcelWriter(file_path) as writer: + df_gem.to_excel(writer, sheet_name='Gemiddeld', index=True) + df_kar.to_excel(writer, sheet_name='Karakteristiek', index=True) + + # Format the Excel sheets + format_excel_sheet( + file_path=file_path, + sheet_name='Gemiddeld', + num_columns=len(df_gem.columns), + num_rows=len(df_gem), + table_name='GemiddeldResultaten', + index=True + ) + + format_excel_sheet( + file_path=file_path, + sheet_name='Karakteristiek', + num_columns=len(df_kar.columns), + num_rows=len(df_kar), + table_name='KarakteristiekResultaten', + index=True + ) + + # ========== Handmatige Parameters en wegschrijven ========== + + def set_parameters_handmatig(self, snijpunt_gem, s_gem, m_gem, snijpunt_kar, s_kar, m_kar): + """ + Stelt de handmatige parameters in voor de analyse. De invoer moet handmatig worden gedaan door de gebruiker op basis van de resultaten tabel + """ + self._run_shansep() + self.parameters_handmatig = True + + self.snijpunt_gem_handmatig = snijpunt_gem + self.s_gem_handmatig = s_gem + self.m_gem_handmatig = m_gem + self.pop_gem_handmatig = snijpunt_gem / s_gem / m_gem + + self.snijpunt_kar_handmatig = snijpunt_kar + self.s_kar_handmatig = s_kar + self.m_kar_handmatig = m_kar + self.pop_kar_handmatig = snijpunt_kar / s_kar / m_kar + + # Recalculate standard deviations now that manual parameters are set + self.st_dev_m_handmatig = st_dev_m_handmatig(self) + self.st_dev_pop_handmatig = st_dev_pop_handmatig(self) + self.st_dev_s_handmatig = st_dev_s_handmatig(self) + + def write_analysis_to_excel(self, file_path: str): + """ + Schrijft de analyse dataframes naar een Excel-bestand met verschillende sheets. + + Parameters + ---------- + file_path : str + Pad naar de map waar het Excel-bestand moet worden opgeslagen plus de bestandsnaam + """ + with ExcelWriter(file_path) as writer: + if self.shansep_data_df is not None: + self.shansep_data_df.to_excel(writer, sheet_name='Shansep Data', index=False) + if self.shansep_data_df_oc is not None: + self.shansep_data_df_oc.to_excel(writer, sheet_name='Shansep Data OC', index=False) + if self.shansep_data_df_nc_oc is not None: + self.shansep_data_df_nc_oc.to_excel(writer, sheet_name='Shansep Data NC_OC', index=False) + + # ========== Visualisatie Methodes ========== + + def set_figure_sv_su(self, plot_extra_dataset: Optional[List] = None): + """ + Maakt een visualisatie van de analyseresultaten. + + Parameters + ---------- + plot_extra_dataset : List, optioneel + Extra dataset om in de plot weer te geven + """ + self._run_shansep() + add_proefresultaten_sv_su(self) + if plot_extra_dataset is not None: + add_extra_proefresultaten_sv_su(self, plot_extra_dataset) + add_5pr_bovengrens_sv_su(self) + add_5pr_ondergrens_sv_su(self) + add_lineair_fit_sv_su(self) + + if self.parameters_handmatig: + add_fysische_realiseerbare_ondergrens_sv_su(self) + self.calculate_sutabel() + add_shansep_lijn_sv_su(self) + + set_layout_sv_su(self) + + def set_figure_sv_su_nc(self, plot_extra_dataset: Optional[List] = None): + """ + Maakt een visualisatie van de analyseresultaten. + + Parameters + ---------- + plot_extra_dataset : List, optioneel + Extra dataset om in de plot weer te geven + """ + self._run_shansep() + + self.shansep_data_df_nc = self.shansep_data_df_nc_oc[self.shansep_data_df_nc_oc['consolidatietype'] == 'NC'].copy() if hasattr( + self, 'shansep_data_df_nc_oc') and self.shansep_data_df_nc_oc is not None else None + + add_proefresultaten_sv_su_nc(self) + if plot_extra_dataset is not None: + add_extra_proefresultaten_sv_su_nc(self, plot_extra_dataset) + add_lineair_fit_sv_su_nc(self) + + if self.parameters_handmatig: + add_fysische_realiseerbare_ondergrens_sv_su_nc(self) + self.sutabel_nc = self.calculate_sutabel_nc() + add_shansep_lijn_sv_su_nc(self) + + set_layout_sv_su(self) + + def set_figure_ln_ocr_ln_s(self, plot_extra_dataset: Optional[List] = None): + """ + Maakt een visualisatie van de analyseresultaten. + + Parameters + ---------- + plot_extra_dataset : List, optioneel + Extra dataset om in de plot weer te geven + """ + self._run_shansep() + add_proefresultaten_ln_ocr_ln_s(self) + if plot_extra_dataset is not None: + add_extra_proefresultaten_ln_ocr_ln_s(self, plot_extra_dataset) + add_5pr_bovengrens_ln_ocr_ln_s(self) + add_5pr_ondergrens_ln_ocr_ln_s(self) + add_lineair_fit_ln_ocr_ln_s(self) + + if self.parameters_handmatig: + add_shansep_lijn_ln_ocr_ln_s(self) + + set_layout_ln_ocr_ln_s(self) + + def show_figure_sv_su(self, plot_extra_dataset: Optional[List] = None): + """ + Toont de visualisatie van de analyseresultaten. + + Parameters + ---------- + plot_extra_dataset : List, optioneel + Extra dataset om in de plot weer te geven + """ + self._run_shansep() + self.figure = go.Figure() + self.set_figure_sv_su(plot_extra_dataset) + + def show_figure_ln_ocr_ln_s(self, plot_extra_dataset: Optional[List] = None): + """ + Toont de visualisatie van de analyseresultaten. + + Parameters + ---------- + plot_extra_dataset : List, optioneel + Extra dataset om in de plot weer te geven + """ + self._run_shansep() + self.figure = go.Figure() + self.set_figure_ln_ocr_ln_s(plot_extra_dataset) + self.figure.show() + + def show_figure_sv_su_nc(self, plot_extra_dataset: Optional[List] = None): + """ + Toont de visualisatie van NC (Normal Consolidated) analyseresultaten met sv-su plot. + + Parameters + ---------- + plot_extra_dataset : List, optioneel + Extra dataset om in de plot weer te geven + """ + self._run_shansep() + self.figure = go.Figure() + self.set_figure_sv_su_nc(plot_extra_dataset) + self.figure.show() + + # ========== Export Methodes ========== + + def add_results_to_dbase(self, path: str, file_name: str = 'Template_PVtool5_0.xlsx'): + """ + Voegt de SHANSEP analyseresultaten toe aan de database Excel-bestand. + + Parameters + ---------- + path : str + Pad naar de map waar het Excel-bestand staat + file_name : str, optioneel + Naam van het Excel-bestand (default: 'Template_PVtool5_0.xlsx') + + Returns + ------- + DataFrame + Bijgewerkte DataFrame met alle resultaten + """ + return _add_results_to_dbase(self, path, file_name) + + def save_total_to_excel(self, path: str): + """ + Exporteert alle SHANSEP analysegegevens naar Excel. + + Slaat de volledige dataset met alle berekende kolommen op in een Excel bestand. + De bestandsnaam wordt automatisch gegenereerd op basis van de analyse-instellingen. + + Parameters + ---------- + path : str + Map locatie waar het Excel-bestand moet worden opgeslagen + """ + return _save_total_to_excel(self, path) + + def save_to_pdf(self, path: str) -> str: + """ + Slaat de SHANSEP analyseresultaten op in een PDF-document. + + De PDF bevat: + - Titel met analysedetails + - Beide overzichtsfiguren van de analyse (sv-su en ln(OCR)-ln(su/svc)) + - Su tabel + - Tabel met gemiddelde en karakteristieke resultaten + - Tabel met invoerselectie informatie + + Parameters + ---------- + path : str + Map locatie waar het PDF-bestand moet worden opgeslagen + + Returns + ------- + str + Het absolute bestandspad van het aangemaakte PDF-bestand + """ + return _save_to_pdf(self, path) diff --git a/pv_tool/shansep_analysis/variables.py b/pv_tool/shansep_analysis/variables.py index 951435e..126ba3d 100644 --- a/pv_tool/shansep_analysis/variables.py +++ b/pv_tool/shansep_analysis/variables.py @@ -6,7 +6,7 @@ if TYPE_CHECKING: from pv_tool.shansep_analysis.shansep_analysis import SHANSEP import numpy as np -from scipy.stats import t +from scipy.stats import t, norm # -------------------------- alleen OC --------------------------------------- def count_sv_oc(self: SHANSEP): @@ -37,21 +37,21 @@ def e_a1_oc(self: SHANSEP): return (sum_su_oc(self) - sum_sv_oc(self) * e_a2_oc(self)) / count_sv_oc(self) -def sum_kappa_2_oc(self: SHANSEP): - return self.shansep_data_df_oc['kappa_2'].sum() +def sum_chi_2_oc(self: SHANSEP): + return self.shansep_data_df_oc['chi_2'].sum() def var_a2_oc(self: SHANSEP): - return (1 / sum_sv_tt_oc(self)) * sum_kappa_2_oc(self) / (count_sv_oc(self) - 2) + return (1 / sum_sv_tt_oc(self)) * sum_chi_2_oc(self) / (count_sv_oc(self) - 2) def var_a1_oc(self: SHANSEP): - return 1 / count_sv_oc(self) * (1 + sum_sv_oc(self) ** 2 / (count_sv_oc(self) * sum_sv_tt_oc(self))) * sum_kappa_2_oc(self) / ( + return 1 / count_sv_oc(self) * (1 + sum_sv_oc(self) ** 2 / (count_sv_oc(self) * sum_sv_tt_oc(self))) * sum_chi_2_oc(self) / ( count_sv_oc(self) - 2) def cov_a1_a2_oc(self: SHANSEP): - return -(sum_sv_oc(self) / (count_sv_oc(self) * sum_sv_tt_oc(self))) * sum_kappa_2_oc(self) / (count_sv_oc(self) - 2) + return -(sum_sv_oc(self) / (count_sv_oc(self) * sum_sv_tt_oc(self))) * sum_chi_2_oc(self) / (count_sv_oc(self) - 2) def rho_a1_a2_oc(self: SHANSEP): @@ -71,6 +71,8 @@ def t_n_2_oc(self: SHANSEP): degrees_of_freedom = count_sv_oc(self) - 2 return t.ppf(1 - significantieniveau / 2, degrees_of_freedom) +# - - - - - - - - - - - - - - op blauwe kolommen in excel - - - - - - - - - - - - - - - - - + def sum_s_eff_oc(self: SHANSEP): return self.shansep_data_df_oc['s\''].sum() @@ -92,17 +94,44 @@ def a2_kar_oc(self: SHANSEP): def a1_kar_oc(self: SHANSEP): return (sum_5_pr_ondergrens_oc(self) - a2_kar_oc(self) * sum_s_eff_oc(self)) / count_s_eff_oc(self) +# ------------------ bepaling s en pop op basis van nc proeven ---------------- +def gem_ln_su_svc_nc(self: SHANSEP): + shansep_data_df_nc = self.shansep_data_df_nc_oc[self.shansep_data_df_nc_oc['consolidatietype'] == 'NC'] + return shansep_data_df_nc['LN(su/svc)'].mean() + +def exp_gem_ln_su_svc_nc(self: SHANSEP): + return math.exp(gem_ln_su_svc_nc(self)) + +def std_ln_su_svc_nc(self: SHANSEP): + shansep_data_df_nc = self.shansep_data_df_nc_oc[self.shansep_data_df_nc_oc['consolidatietype'] == 'NC'] + return shansep_data_df_nc['LN(su/svc)'].std(ddof=1) + +def kar_ln_su_svc_nc(self: SHANSEP): + return gem_ln_su_svc_nc(self) - std_ln_su_svc_nc(self)*t_n_2_oc(self) * ((1-self.alpha)+1/count_sv_oc(self))**0.5 + +def exp_kar_ln_su_svc_nc(self: SHANSEP): + return math.exp(kar_ln_su_svc_nc(self)) + +def gem_pop_oc(self: SHANSEP): + return self.shansep_data_df_oc['POP'].mean() + +def std_pop_oc(self: SHANSEP): + return self.shansep_data_df_oc['POP'].std(ddof=1) + +def kar_pop_oc(self: SHANSEP): + return gem_pop_oc(self) + std_pop_oc(self)*t_n_2_oc(self) * ((1-self.alpha)+1/count_sv_oc(self))**0.5 + # -------------------------- NC en OC --------------------------------------- -def count_sv_nc_oc(self: SHANSEP): +def count_ln_ocr_nc_oc(self: SHANSEP): return self.shansep_data_df_nc_oc['LN(OCR)'].count() -def sum_sv_nc_oc(self: SHANSEP): +def sum_ln_ocr_nc_oc(self: SHANSEP): return self.shansep_data_df_nc_oc['LN(OCR)'].sum() -def sum_su_nc_oc(self: SHANSEP): - return self.shansep_data_df_nc_oc['Su'].sum() +def sum_ln_su_svc_nc_oc(self: SHANSEP): + return self.shansep_data_df_nc_oc['LN(su/svc)'].sum() def sum_sv_tt_nc_oc(self: SHANSEP): @@ -118,24 +147,24 @@ def e_a2_nc_oc(self: SHANSEP): def e_a1_nc_oc(self: SHANSEP): - return (sum_su_nc_oc(self) - sum_sv_nc_oc(self) * e_a2_nc_oc(self)) / count_sv_nc_oc(self) + return (sum_ln_su_svc_nc_oc(self) - sum_ln_ocr_nc_oc(self) * e_a2_nc_oc(self)) / count_ln_ocr_nc_oc(self) -def sum_kappa_2_nc_oc(self: SHANSEP): - return self.shansep_data_df_nc_oc['kappa_2'].sum() +def sum_chi_2_nc_oc(self: SHANSEP): + return self.shansep_data_df_nc_oc['chi_2'].sum() def var_a2_nc_oc(self: SHANSEP): - return (1 / sum_sv_tt_nc_oc(self)) * sum_kappa_2_nc_oc(self) / (count_sv_nc_oc(self) - 2) + return (1 / sum_sv_tt_nc_oc(self)) * sum_chi_2_nc_oc(self) / (count_ln_ocr_nc_oc(self) - 2) def var_a1_nc_oc(self: SHANSEP): - return 1 / count_sv_nc_oc(self) * (1 + sum_sv_nc_oc(self) ** 2 / (count_sv_nc_oc(self) * sum_sv_tt_nc_oc(self))) * sum_kappa_2_nc_oc(self) / ( - count_sv_nc_oc(self) - 2) + return 1 / count_ln_ocr_nc_oc(self) * (1 + sum_ln_ocr_nc_oc(self) ** 2 / (count_ln_ocr_nc_oc(self) * sum_sv_tt_nc_oc(self))) * sum_chi_2_nc_oc(self) / ( + count_ln_ocr_nc_oc(self) - 2) def cov_a1_a2_nc_oc(self: SHANSEP): - return -(sum_sv_nc_oc(self) / (count_sv_nc_oc(self) * sum_sv_tt_nc_oc(self))) * sum_kappa_2_nc_oc(self) / (count_sv_nc_oc(self) - 2) + return -(sum_ln_ocr_nc_oc(self) / (count_ln_ocr_nc_oc(self) * sum_sv_tt_nc_oc(self))) * sum_chi_2_nc_oc(self) / (count_ln_ocr_nc_oc(self) - 2) def rho_a1_a2_nc_oc(self: SHANSEP): @@ -152,7 +181,7 @@ def sigma_a1_nc_oc(self: SHANSEP): def t_n_2_nc_oc(self: SHANSEP): significantieniveau = 0.1 - degrees_of_freedom = count_sv_nc_oc(self) - 2 + degrees_of_freedom = count_ln_ocr_nc_oc(self) - 2 return t.ppf(1 - significantieniveau / 2, degrees_of_freedom) def sum_s_eff_nc_oc(self: SHANSEP): @@ -176,3 +205,87 @@ def a2_kar_nc_oc(self: SHANSEP): def a1_kar_nc_oc(self: SHANSEP): return (sum_5_pr_ondergrens_nc_oc(self) - a2_kar_nc_oc(self) * sum_s_eff_nc_oc(self)) / count_s_eff_nc_oc(self) +# --------------------- stdev van resultaten ----------------------- +def st_dev_s_handmatig(self: SHANSEP): + """Berekent de standaarddeviatie van s op basis van gemiddelde en rekenwaarde. + + Returns: + float or None: De standaarddeviatie van s, of None als de berekening niet mogelijk is. + """ + import warnings + + s_gem = self.s_gem_handmatig + s_kar = self.s_kar_handmatig + + # Controleer op fysisch onmogelijke waarden - check None first + if s_gem is None or s_gem <= 0: + warnings.warn(f"Gemiddelde s waarde moet positief zijn voor berekenen standaarddeviatie, gevonden waarde: {s_gem}") + return None + if s_kar is None or s_kar <= 0: + warnings.warn(f"Karakteristieke s waarde moet positief zijn voor berekenen standaarddeviatie, gevonden waarde: {s_kar}") + return None + + try: + st_dev = s_gem * math.sqrt(math.exp((((norm.ppf(0.05) * 2) + math.sqrt((norm.ppf(0.05) * 2) ** 2 + + 8 * (math.log(s_gem) - math.log(s_kar)))) + / 2) ** 2) - 1) + return float(st_dev) + except Exception as e: + warnings.warn(f"Kon standaarddeviatie van s niet berekenen: {e}") + return None + +def st_dev_m_handmatig(self: SHANSEP): + """Berekent de standaarddeviatie van m op basis van gemiddelde en rekenwaarde. + + Returns: + float or None: De standaarddeviatie van m, of None als de berekening niet mogelijk is. + """ + import warnings + + m_gem = self.m_gem_handmatig + m_kar = self.m_kar_handmatig + + # Controleer op fysisch onmogelijke waarden - check None first + if m_gem is None or m_gem <= 0: + warnings.warn(f"Gemiddelde m waarde moet positief zijn voor berekenen standaarddeviatie, gevonden waarde: {m_gem}") + return None + if m_kar is None or m_kar <= 0: + warnings.warn(f"Karakteristieke m waarde moet positief zijn voor berekenen standaarddeviatie, gevonden waarde: {m_kar}") + return None + + try: + st_dev = m_gem * math.sqrt(math.exp((((norm.ppf(0.05) * 2) + math.sqrt((norm.ppf(0.05) * 2) ** 2 + + 8 * (math.log(m_gem) - math.log(m_kar)))) + / 2) ** 2) - 1) + return float(st_dev) + except Exception as e: + warnings.warn(f"Kon standaarddeviatie van m niet berekenen: {e}") + return None + +def st_dev_pop_handmatig(self: SHANSEP): + """Berekent de standaarddeviatie van pop op basis van gemiddelde en rekenwaarde. + + Returns: + float or None: De standaarddeviatie van pop, of None als de berekening niet mogelijk is. + """ + import warnings + + pop_gem = self.pop_gem_handmatig + pop_kar = self.pop_kar_handmatig + + # Controleer op fysisch onmogelijke waarden - check None first + if pop_gem is None or pop_gem <= 0: + warnings.warn(f"Gemiddelde pop waarde moet positief zijn voor berekenen standaarddeviatie, gevonden waarde: {pop_gem}") + return None + if pop_kar is None or pop_kar <= 0: + warnings.warn(f"Karakteristieke pop waarde moet positief zijn voor berekenen standaarddeviatie, gevonden waarde: {pop_kar}") + return None + + try: + st_dev = pop_gem * math.sqrt(math.exp((((norm.ppf(0.05) * 2) + math.sqrt((norm.ppf(0.05) * 2) ** 2 + + 8 * (math.log(pop_gem) - math.log(pop_kar)))) + / 2) ** 2) - 1) + return float(st_dev) + except Exception as e: + warnings.warn(f"Kon standaarddeviatie van pop niet berekenen: {e}") + return None \ No newline at end of file diff --git a/pv_tool/shansep_analysis/visualization_shansep.py b/pv_tool/shansep_analysis/visualization_shansep.py new file mode 100644 index 0000000..a7eb118 --- /dev/null +++ b/pv_tool/shansep_analysis/visualization_shansep.py @@ -0,0 +1,514 @@ +from __future__ import annotations +from typing import TYPE_CHECKING +import math + +if TYPE_CHECKING: + from pv_tool.shansep_analysis.shansep_analysis import SHANSEP +import plotly.graph_objects as go +from typing import Optional, List +from pv_tool.shansep_analysis.globals import (TEXTUAL_NAMES, TEXTUAL_NAMES_DSS, NEW_COLUMN_NAMES) +import numpy as np + +# ----------------------------------- proefresultaten ----------------------------------- + +def add_proefresultaten_sv_su(self: SHANSEP): + """Deze functie voegt de proefresultaten toe aan de figuur.""" + boring_monsternummer = self.shansep_data_df_oc.index + + x_proefresultaten = self.shansep_data_df_oc['S\'v'] + y_proefresultaten = self.shansep_data_df_oc['Su'] + + self.figure.add_trace( + go.Scatter( + x=x_proefresultaten, + y=y_proefresultaten, + mode='markers', + marker=dict( + color='blue' + ), + name=f'Geanalyseerd: {self.investigation_groups[0]} (OC)', + text=boring_monsternummer, + hoverinfo='text' + ) + ) + +def add_proefresultaten_sv_su_nc(self: SHANSEP): + """Deze functie voegt de proefresultaten toe aan de figuur.""" + boring_monsternummer = self.shansep_data_df_nc.index + + x_proefresultaten = self.shansep_data_df_nc['S\'v'] + y_proefresultaten = self.shansep_data_df_nc['Su'] + + self.figure.add_trace( + go.Scatter( + x=x_proefresultaten, + y=y_proefresultaten, + mode='markers', + marker=dict( + color='blue' + ), + name=f'Geanalyseerd: {self.investigation_groups[0]} (NC)', + text=boring_monsternummer, + hoverinfo='text' + ) + ) + +def add_proefresultaten_ln_ocr_ln_s(self: SHANSEP): + """Deze functie voegt de proefresultaten toe aan de figuur.""" + boring_monsternummer = self.shansep_data_df_oc.index + + x_proefresultaten = self.shansep_data_df_nc_oc['LN(OCR)'] + y_proefresultaten = self.shansep_data_df_nc_oc['LN(su/svc)'] + + self.figure.add_trace( + go.Scatter( + x=x_proefresultaten, + y=y_proefresultaten, + mode='markers', + marker=dict( + color='blue' + ), + name=f'Geanalyseerd: {self.investigation_groups[0]}', + text=boring_monsternummer, + hoverinfo='text' + ) + ) + +def get_extra_data(self: SHANSEP, investigationgroups_extra: Optional[List]): + if self.analysis_type in ['TXT_S_POP']: + dataset_df = self.dbase_df[self.dbase_df['ALG__TRIAXIAAL']] + elif self.analysis_type in ['DSS_S_POP']: + dataset_df = self.dbase_df[self.dbase_df['ALG__DSS']] + else: + raise ValueError(f"analysis type for extra dataset not right: {self.analysis_type}") + + dataset_df = dataset_df[ + dataset_df['PV_NAAM'].isin(investigationgroups_extra)] + if self.analysis_type in ['DSS_S_POP']: + dataset_df = dataset_df[TEXTUAL_NAMES_DSS.get(self.effective_stress, [])] + else: + dataset_df = dataset_df[TEXTUAL_NAMES.get(self.effective_stress, [])] + dataset_df.columns = NEW_COLUMN_NAMES + return dataset_df + +def add_extra_proefresultaten_sv_su(self: SHANSEP, extra_groepen: Optional[List]): + """Deze functie voegt de proefresultaten toe aan de figuur.""" + df = get_extra_data(self, investigationgroups_extra=extra_groepen) + df = df[df['consolidatietype'] == 'OC'].copy() + boring_monsternummer = df.index + + n=0 + for naam in df['PV_NAAM'].unique(): + + sub_df = df[df['PV_NAAM'] == naam] + x_extra_proefresultaten = sub_df['S\'v'] + y_extra_proefresultaten = sub_df['Su'] + + self.figure.add_trace( + go.Scatter( + x=x_extra_proefresultaten, + y=y_extra_proefresultaten, + mode='markers', + name=f'Extra: {extra_groepen[n]}', + text=boring_monsternummer, + hoverinfo='text' + ) + ) + n += 1 + +def add_extra_proefresultaten_ln_ocr_ln_s(self: SHANSEP, extra_groepen: Optional[List]): + """Deze functie voegt de proefresultaten toe aan de figuur.""" + df = get_extra_data(self, investigationgroups_extra=extra_groepen) + boring_monsternummer = df.index + + # paar zaken berekenen van deze nieuwe data + df.loc[:, 'LN(OCR)'] = ( + df['OCR'].apply + (lambda x: np.log(x) if x is not None and x > 0 else "")) + + df.loc[:, 'S'] = df['Su'] / df['S\'v'] + + df.loc[:, 'LN(su/svc)'] = ( + df['S'].apply + (lambda x: np.log(x) if x is not None and x > 0 else "")) + + n=0 + for naam in df['PV_NAAM'].unique(): + + sub_df = df[df['PV_NAAM'] == naam] + x_extra_proefresultaten = sub_df['LN(OCR)'] + y_extra_proefresultaten = sub_df['LN(su/svc)'] + + self.figure.add_trace( + go.Scatter( + x=x_extra_proefresultaten, + y=y_extra_proefresultaten, + mode='markers', + name=f'Extra: {extra_groepen[n]}', + text=boring_monsternummer, + hoverinfo='text' + ) + ) + n += 1 + +def add_extra_proefresultaten_sv_su_nc(self: SHANSEP, extra_groepen: Optional[List]): + """Deze functie voegt de proefresultaten toe aan de figuur.""" + df = get_extra_data(self, investigationgroups_extra=extra_groepen) + + df = df[df['consolidatietype'] == 'NC'].copy() + + boring_monsternummer = df.index + + n=0 + for naam in df['PV_NAAM'].unique(): + + sub_df = df[df['PV_NAAM'] == naam] + x_extra_proefresultaten = sub_df['S\'v'] + y_extra_proefresultaten = sub_df['Su'] + + self.figure.add_trace( + go.Scatter( + x=x_extra_proefresultaten, + y=y_extra_proefresultaten, + mode='markers', + name=f'Extra: {extra_groepen[n]}', + text=boring_monsternummer, + hoverinfo='text' + ) + ) + n += 1 + +# ----------------------------------- statistiek ---------------------------------------- + +def add_5pr_bovengrens_sv_su(self: SHANSEP): + """Deze functie voegt de 5% bovengrens toe aan de figuur.""" + x_5pr = self.shansep_data_df_oc['s\''] + y_5pr = self.shansep_data_df_oc['5_pr_bovengrens'] + + self.figure.add_trace( + go.Scatter( + x=x_5pr, + y=y_5pr, + mode='lines', + name='5% bovengrens', + line=dict( + color='black', + width=1, + dash='dash' + ) + ) + ) + +def add_5pr_ondergrens_sv_su(self: SHANSEP): + """Deze functie voegt de 5% bovengrens toe aan de figuur.""" + x_5pr = self.shansep_data_df_oc['s\''] + y_5pr = self.shansep_data_df_oc['5_pr_ondergrens'] + + self.figure.add_trace( + go.Scatter( + x=x_5pr, + y=y_5pr, + mode='lines', + name='5% ondergrens', + line=dict( + color='black', + width=1, + dash='dash' + ) + ) + ) + +def add_5pr_bovengrens_ln_ocr_ln_s(self: SHANSEP): + """Deze functie voegt de 5% bovengrens toe aan de figuur.""" + x_5pr = self.shansep_data_df_nc_oc['s\''] + y_5pr = self.shansep_data_df_nc_oc['5_pr_bovengrens'] + + self.figure.add_trace( + go.Scatter( + x=x_5pr, + y=y_5pr, + mode='lines', + name='5% bovengrens', + line=dict( + color='black', + width=1, + dash='dash' + ) + ) + ) + +def add_5pr_ondergrens_ln_ocr_ln_s(self: SHANSEP): + """Deze functie voegt de 5% bovengrens toe aan de figuur.""" + x_5pr = self.shansep_data_df_nc_oc['s\''] + y_5pr = self.shansep_data_df_nc_oc['5_pr_ondergrens'] + + self.figure.add_trace( + go.Scatter( + x=x_5pr, + y=y_5pr, + mode='lines', + name='5% ondergrens', + line=dict( + color='black', + width=1, + dash='dash' + ) + ) + ) + +def add_fysische_realiseerbare_ondergrens_sv_su(self: SHANSEP): + """Deze functie voegt de fysische realiseerbare ondergrens toe aan de figuur.""" + raaklijn_kar_x1 = 0 + raaklijn_kar_x2 = self.shansep_data_df_oc['S\'v'].max() + 5 + + raaklijn_kar_y1 = self.snijpunt_kar_handmatig + (raaklijn_kar_x1 * self.s_kar_handmatig) + raaklijn_kar_y2 = self.snijpunt_kar_handmatig + (raaklijn_kar_x2 * self.s_kar_handmatig) + + x = [raaklijn_kar_x1, raaklijn_kar_x2] + y = [raaklijn_kar_y1, raaklijn_kar_y2] + + self.figure.add_trace( + go.Scatter( + x=x, + y=y, + mode='lines', + name='Fysische realiseerbare ondergrens', + line=dict(color='black', width=2), + ) + ) + +def add_fysische_realiseerbare_ondergrens_sv_su_nc(self: SHANSEP): + """Deze functie voegt de fysische realiseerbare ondergrens toe aan de figuur.""" + raaklijn_kar_x1 = 0 + raaklijn_kar_x2 = self.shansep_data_df_nc['S\'v'].max() + 5 + + raaklijn_kar_y1 = 0 + (raaklijn_kar_x1 * self.s_kar_handmatig) + raaklijn_kar_y2 = 0 + (raaklijn_kar_x2 * self.s_kar_handmatig) + + x = [raaklijn_kar_x1, raaklijn_kar_x2] + y = [raaklijn_kar_y1, raaklijn_kar_y2] + + self.figure.add_trace( + go.Scatter( + x=x, + y=y, + mode='lines', + name='Fysische realiseerbare ondergrens', + line=dict(color='black', width=2), + ) + ) + +def _get_helling_value(helling): + """Helper function om de juiste waarde uit helling te halen, ongeacht of het een float of array is.""" + if isinstance(helling, (list, np.ndarray)): + return float(helling[0]) + return float(helling) + +def add_lineair_fit_sv_su(self: SHANSEP): + """Deze functie voegt de lineaire fit van de proefresultaten toe aan de figuur.""" + x1 = self.shansep_data_df_oc['S\'v'].min() + x2 = self.shansep_data_df_oc['S\'v'].max() + + # lineaire fit helling berekenen + x_data = self.shansep_data_df_oc['S\'v'].values + y_data = self.shansep_data_df_oc['Su'].values + helling, intercept = np.polyfit(x_data, y_data, 1) + + y1 = x1 * helling + intercept + y2 = x2 * helling + intercept + + x = [x1, x2] + y = [y1, y2] + + self.figure.add_trace( + go.Scatter( + x=x, + y=y, + mode='lines', + name='Lineaire fit proefresultaten', + line=dict(color='green', width=2), + ) + ) + +def add_lineair_fit_sv_su_nc(self: SHANSEP): + """Deze functie voegt de lineaire fit van de proefresultaten toe aan de figuur.""" + x1 = self.shansep_data_df_nc['S\'v'].min() + x2 = self.shansep_data_df_nc['S\'v'].max() + + x_data = self.shansep_data_df_nc['S\'v'].values + y_data = self.shansep_data_df_nc['Su'].values + helling, intercept = np.polyfit(x_data, y_data, 1) + + y1 = x1 * helling + intercept + y2 = x2 * helling + intercept + + x = [x1, x2] + y = [y1, y2] + + self.figure.add_trace( + go.Scatter( + x=x, + y=y, + mode='lines', + name='Lineaire fit proefresultaten', + line=dict(color='green', width=2), + ) + ) + +def add_lineair_fit_ln_ocr_ln_s(self: SHANSEP): + """Deze functie voegt de lineaire fit van de proefresultaten toe aan de figuur.""" + x1 = self.shansep_data_df_nc_oc['LN(OCR)'].min() + x2 = self.shansep_data_df_nc_oc['LN(OCR)'].max() + + x_data = self.shansep_data_df_nc_oc['LN(OCR)'].values + y_data = self.shansep_data_df_nc_oc['LN(su/svc)'].values + helling, intercept = np.polyfit(x_data, y_data, 1) + + y1 = x1 * helling + intercept + y2 = x2 * helling + intercept + + x = [x1, x2] + y = [y1, y2] + + self.figure.add_trace( + go.Scatter( + x=x, + y=y, + mode='lines', + name='Lineaire fit proefresultaten', + line=dict(color='green', width=2), + ) + ) + +# ----------------------------------- shansep ---------------------------------------- + +def add_shansep_lijn_sv_su(self: SHANSEP): + x = self.sutabel['S\'v [kPa]'].tolist() + shansep_kar = self.sutabel['Su in-situ karakteristiek'].tolist() + shansep_gem = self.sutabel['Su in-situ gemiddeld'].tolist() + + self.figure.add_trace( + go.Scatter( + x=x, + y=shansep_gem, + mode='lines', + name='SHANSEP gemiddelde lijn', + line=dict(color='purple', width=2, dash='dot') + ) + ) + + self.figure.add_trace( + go.Scatter( + x=x, + y=shansep_kar, + mode='lines', + name='SHANSEP karakteristieke lijn', + line=dict(color='purple', width=2) + ) + ) + +def add_shansep_lijn_sv_su_nc(self: SHANSEP): + if self.sutabel_nc is None: + self.calculate_sutabel_nc() + x = self.sutabel_nc['S\'v [kPa]'].tolist() + shansep_kar = self.sutabel_nc['Su in-situ karakteristiek'].tolist() + shansep_gem = self.sutabel_nc['Su in-situ gemiddeld'].tolist() + + self.figure.add_trace( + go.Scatter( + x=x, + y=shansep_gem, + mode='lines', + name='SHANSEP gemiddelde lijn', + line=dict(color='purple', width=2, dash='dot') + ) + ) + + self.figure.add_trace( + go.Scatter( + x=x, + y=shansep_kar, + mode='lines', + name='SHANSEP karakteristieke lijn', + line=dict(color='purple', width=2) + ) + ) + +def add_shansep_lijn_ln_ocr_ln_s(self: SHANSEP): + s = [0, 1.01] + + a2gem = self.m_gem_handmatig + a1gem = math.log(self.s_gem_handmatig) + + a2kar = self.m_kar_handmatig + a1kar = math.log(self.s_kar_handmatig) + + t_gem = [a1gem+(x*a2gem) for x in s] + t_kar = [a1kar+(x*a2kar) for x in s] + + + self.figure.add_trace( + go.Scatter( + x=s, + y=t_gem, + mode='lines', + name='SHANSEP gemiddelde lijn', + line=dict(color='purple', width=2, dash='dot') + ) + ) + + self.figure.add_trace( + go.Scatter( + x=s, + y=t_kar, + mode='lines', + name='SHANSEP karakteristieke lijn', + line=dict(color='purple', width=2) + ) + ) + +# ----------------------------------- layout ---------------------------------------- + +def set_layout_sv_su(self: SHANSEP): + """ + Stelt de layout van de figuur in met titel en as-labels. + + De figuurgrootte is geoptimaliseerd voor zowel schermdisplay als PDF-export. + """ + title = f'Bepaling S en POP uit {self.analysis_type} proef' + + xas_title = r"$\sigma'_{v} [kPa]$" + yas_title = r'$s_{u} [kPa]$' + + legend_title = 'Legenda' + self.figure.update_layout( + width=1280, + height=720, + title=title if self.show_title else None, + xaxis_title=xas_title, + yaxis_title=yas_title, + legend_title=legend_title, + margin=dict(t=100, r=50, b=100, l=50) + ) + +def set_layout_ln_ocr_ln_s(self: SHANSEP): + """ + Stelt de layout van de figuur in met titel en as-labels. + + De figuurgrootte is geoptimaliseerd voor zowel schermdisplay als PDF-export. + """ + title = f'Bepaling op basis van S en m op {self.analysis_type} proef' + + xas_title = r'$LN(OCR) [-]$' + yas_title = r"$LN(s_{u}/\sigma'_{v}) [-]$" + + legend_title = 'Legenda' + self.figure.update_layout( + width=1280, + height=720, + title=title if self.show_title else None, + xaxis_title=xas_title, + yaxis_title=yas_title, + legend_title=legend_title, + margin=dict(t=100, r=50, b=100, l=50) + ) diff --git a/pv_tool/sutabel_analysis/__init__.py b/pv_tool/sutabel_analysis/__init__.py new file mode 100644 index 0000000..c9e825b --- /dev/null +++ b/pv_tool/sutabel_analysis/__init__.py @@ -0,0 +1,15 @@ +""" +Sutabel-m Analysis Module + +Deze module bevat alle functionaliteit voor sutabel-m analyses. +De sutabel-m methode is een alternatieve methode voor het bepalen van +ongedraineerde schuifsterkte op basis van overgeconsolideerde (OC) proeven. + +Hoofdklasse: + SUTABEL: Klasse voor het uitvoeren van sutabel-m analyses +""" + +from pv_tool.sutabel_analysis.sutabel_analysis import SUTABEL + +__all__ = ['SUTABEL'] + diff --git a/pv_tool/sutabel_analysis/expand_analysis.py b/pv_tool/sutabel_analysis/expand_analysis.py new file mode 100644 index 0000000..cfd40d3 --- /dev/null +++ b/pv_tool/sutabel_analysis/expand_analysis.py @@ -0,0 +1,134 @@ +""" +Expand Analysis functies voor Sutabel-m analyse. + +Deze module bevat functies voor het uitbreiden van de analyse dataframe +met berekende kolommen voor sutabel-m analyses. +""" + +from typing import TYPE_CHECKING +import numpy as np +import pandas as pd +from pandas import DataFrame + +if TYPE_CHECKING: + from pv_tool.sutabel_analysis.sutabel_analysis import SUTABEL + + +def calculate_ln_sv_sutabel(self: "SUTABEL"): + """Berekent ln(s'v) voor de sutabel dataframe.""" + self.sutabel_data_df.loc[:, 'ln(s\'v)'] = ( + self.sutabel_data_df['S\'v'].apply + (lambda x: np.log(x) if x is not None and x > 0 else "")) + +def calculate_ln_su_sutabel(self: "SUTABEL"): + """Berekent ln(su) voor de sutabel dataframe.""" + self.sutabel_data_df.loc[:, 'ln(su)'] = ( + self.sutabel_data_df['Su'].apply + (lambda x: np.log(x) if x is not None and x > 0 else "")) + +def calculate_sv_tt_sutabel(self: "SUTABEL"): + """Berekent s_tt voor sutabel analyse.""" + from pv_tool.sutabel_analysis.variables import count_ln_sv_sutabel, sum_ln_sv_sutabel + if self.sutabel_data_df is None or self.sutabel_data_df.empty: + raise ValueError("sutabel_data_df is None of leeg. Run de analyse en controleer je data.") + count = count_ln_sv_sutabel(self) + if count == 0: + raise ValueError("Aantal ln(s'v) waarden is 0. Kan niet delen door nul.") + df: DataFrame = self.sutabel_data_df.copy() + # Zorg dat kolom numeriek is + df['ln(s\'v)'] = pd.to_numeric(df['ln(s\'v)'], errors='coerce') + mean_sv = sum_ln_sv_sutabel(self) / count + df['s_tt'] = (df['ln(s\'v)'] - mean_sv) ** 2 + self.sutabel_data_df = df + +def calculate_sv_ty_sutabel(self: "SUTABEL"): + """Berekent s_ty voor sutabel analyse.""" + from pv_tool.sutabel_analysis.variables import count_ln_sv_sutabel, sum_ln_sv_sutabel + if self.sutabel_data_df is None or self.sutabel_data_df.empty: + raise ValueError("sutabel_data_df is None of leeg. Run de analyse en controleer je data.") + count = count_ln_sv_sutabel(self) + if count == 0: + raise ValueError("Aantal ln(s'v) waarden is 0. Kan niet delen door nul.") + df: DataFrame = self.sutabel_data_df.copy() + # Zorg dat kolommen numeriek zijn + df['ln(s\'v)'] = pd.to_numeric(df['ln(s\'v)'], errors='coerce') + df['ln(su)'] = pd.to_numeric(df['ln(su)'], errors='coerce') + mean_s = sum_ln_sv_sutabel(self) / count + df['s_ty'] = (df['ln(s\'v)'] - mean_s) * df['ln(su)'] + self.sutabel_data_df = df + +def calculate_chi_2_sutabel(self: "SUTABEL"): + """Berekent chi_2 voor sutabel analyse.""" + from pv_tool.sutabel_analysis.variables import e_a1_sutabel, e_a2_sutabel + formule = (self.sutabel_data_df['ln(su)'] - e_a1_sutabel(self) - + e_a2_sutabel(self) * self.sutabel_data_df['ln(s\'v)']) ** 2 + self.sutabel_data_df['chi_2'] = formule + +def s_min_sutabel(self: "SUTABEL"): + """Berekent minimum ln(s'v) waarde voor sutabel analyse.""" + return self.sutabel_data_df['ln(s\'v)'].min() + +def s_max_sutabel(self: "SUTABEL"): + """Berekent maximum ln(s'v) waarde voor sutabel analyse.""" + return self.sutabel_data_df['ln(s\'v)'].max() + +def calculate_sv_eff_sutabel(self: "SUTABEL"): + """Berekent s' waarden met gelijke intervallen tussen min en max voor sutabel analyse.""" + from pv_tool.sutabel_analysis.variables import count_ln_sv_sutabel + lijst = [s_min_sutabel(self)] + formule = (s_max_sutabel(self) - s_min_sutabel(self)) / (count_ln_sv_sutabel(self) - 1) + aantal_waarden = len(self.sutabel_data_df) + for i in range(1, aantal_waarden): + nieuwe_waarde = lijst[i - 1] + formule + lijst.append(nieuwe_waarde) + self.sutabel_data_df['s\''] = lijst + +def calculate_5pr_ondergrens_sutabel(self: "SUTABEL"): + """Berekent 5% ondergrens voor sutabel analyse.""" + from pv_tool.sutabel_analysis.variables import (e_a1_sutabel, e_a2_sutabel, t_n_2_sutabel, + sigma_a1_sutabel, sigma_a2_sutabel, + rho_a1_a2_sutabel, sum_chi_2_sutabel, + count_ln_sv_sutabel) + formule = ( + e_a1_sutabel(self) + + e_a2_sutabel(self) * self.sutabel_data_df['s\''] - t_n_2_sutabel(self) * + (sigma_a1_sutabel(self) ** 2 + self.sutabel_data_df['s\''] ** 2 * sigma_a2_sutabel(self) ** 2 + + 2 * rho_a1_a2_sutabel(self) * self.sutabel_data_df['s\''] * sigma_a1_sutabel(self) * sigma_a2_sutabel(self) + + (1.0 - self.alpha) * (sum_chi_2_sutabel(self) / (count_ln_sv_sutabel(self) - 2))) ** 0.5 + ) + self.sutabel_data_df['5_pr_ondergrens'] = formule + +def calculate_5pr_bovengrens_sutabel(self: "SUTABEL"): + """Berekent 5% bovengrens voor sutabel analyse.""" + from pv_tool.sutabel_analysis.variables import (e_a1_sutabel, e_a2_sutabel, t_n_2_sutabel, + sigma_a1_sutabel, sigma_a2_sutabel, + rho_a1_a2_sutabel, sum_chi_2_sutabel, + count_ln_sv_sutabel) + formule = ( + e_a1_sutabel(self) + + e_a2_sutabel(self) * self.sutabel_data_df['s\''] + t_n_2_sutabel(self) * + (sigma_a1_sutabel(self) ** 2 + self.sutabel_data_df['s\''] ** 2 * sigma_a2_sutabel(self) ** 2 + + 2 * rho_a1_a2_sutabel(self) * self.sutabel_data_df['s\''] * sigma_a1_sutabel(self) * sigma_a2_sutabel(self) + + (1 - self.alpha) * (sum_chi_2_sutabel(self) / (count_ln_sv_sutabel(self) - 2))) ** 0.5 + ) + self.sutabel_data_df['5_pr_bovengrens'] = formule + +def calculate_sv_tt_ondergrens_sutabel(self: "SUTABEL"): + """Berekent s_tt ondergrens voor sutabel analyse.""" + from pv_tool.sutabel_analysis.variables import sum_s_eff_sutabel, count_s_eff_sutabel + formule = (self.sutabel_data_df['s\''] - sum_s_eff_sutabel(self) / count_s_eff_sutabel(self)) ** 2 + self.sutabel_data_df['s_tt_ondergrens'] = formule + +def calculate_sv_ty_ondergrens_sutabel(self: "SUTABEL"): + """Berekent s_ty ondergrens voor sutabel analyse.""" + from pv_tool.sutabel_analysis.variables import sum_s_eff_sutabel, count_s_eff_sutabel + formule = (self.sutabel_data_df['s\''] - sum_s_eff_sutabel(self) / + count_s_eff_sutabel(self)) * self.sutabel_data_df['5_pr_ondergrens'] + self.sutabel_data_df['s_ty_ondergrens'] = formule + +def calculate_chi_2_ondergrens_sutabel(self: "SUTABEL"): + """Berekent chi_2 ondergrens voor sutabel analyse.""" + from pv_tool.sutabel_analysis.variables import a1_kar_sutabel, a2_kar_sutabel + formule = (self.sutabel_data_df['5_pr_ondergrens'] - a1_kar_sutabel(self) - + a2_kar_sutabel(self) * self.sutabel_data_df['s\'']) ** 2 + self.sutabel_data_df['chi_2_ondergrens'] = formule diff --git a/pv_tool/sutabel_analysis/save_and_export.py b/pv_tool/sutabel_analysis/save_and_export.py new file mode 100644 index 0000000..eab684d --- /dev/null +++ b/pv_tool/sutabel_analysis/save_and_export.py @@ -0,0 +1,506 @@ +""" +Save and Export functies voor Sutabel-m analyse. + +Deze module bevat functies voor het opslaan en exporteren van sutabel-m analyseresultaten +naar Excel en PDF formaat. +""" + +from typing import TYPE_CHECKING, List +from pandas import ExcelWriter, concat, DataFrame, read_excel +from datetime import datetime +from openpyxl import load_workbook +from reportlab.lib.pagesizes import A4, landscape +from reportlab.lib import colors +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.lib.enums import TA_LEFT +from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, LongTable, PageBreak +from pv_tool.imports.excel_utils import format_excel_sheet +import plotly.graph_objects as go + +try: + from reportlab.platypus import Image as RLImage + from PIL import Image as PILImage + PIL_AVAILABLE = True +except ImportError: + PIL_AVAILABLE = False + print("Waarschuwing: PIL (Pillow) is niet beschikbaar. Figuren kunnen niet aan PDF worden toegevoegd.") + +if TYPE_CHECKING: + from pv_tool.sutabel_analysis.sutabel_analysis import SUTABEL + + +def add_sutabel_results_to_dbase(self: "SUTABEL", path: str, file_name: str = 'Template_PVtool5_0.xlsx'): + """ + Voegt de sutabel-m analyseresultaten toe aan de database Excel-bestand. + + Parameters + ---------- + self : sutabel + Instantie van de sutabel analyse klasse + path : str + Pad naar de map waar het Excel-bestand staat + file_name : str + Naam van het Excel-bestand + + Returns + ------- + DataFrame + Bijgewerkte DataFrame met alle resultaten + """ + file_path = f"{path}/{file_name}" + + if self.sutabel_grafiek is None or self.e_a1_sutabel is None: + self._run_sutabel() + if hasattr(self, 'vc_fit_kar_sutabel') and self.vc_fit_kar_sutabel is not None: + self.get_sutabel_parameters(vc_fit_kar_sutabel=self.vc_fit_kar_sutabel) + else: + self.get_sutabel_parameters() + + expected_columns = [ + 'PVNAAM', 'PV_REK', 'PV_TYPE_PROEF', 'PV_ANALYSE', 'PV_RESULTAAT_ID', 'PV_TYPEVERZAMELING', + 'PV_e_a1_GEM [-]', 'PV_e_a2_GEM [-]', 'PV_svgm_GEM [kPa]', 'PV_m_GEM [-]', + 'PV_a1_KAR [-]', 'PV_a2_KAR [-]', 'PV_svgm_KAR [kPa]', 'PV_m_KAR [-]', + 'PV_vc_FIT_KAR [-]', 'PV_STDEV_LOGN_vc [-]', 'PV_STEYX [-]', + 'PV_VGWNAT_GEM [kN/m3]', 'PV_VGWNAT_SD [kN/m3]', 'PV_WATERGEHALTE_GEM', 'PV_WATERGEHALTE_SD', + 'Timestamp' + ] + + new_row = { + 'PVNAAM': self.investigation_groups[0], + 'PV_REK': self.effective_stress, + 'PV_TYPE_PROEF': self.analysis_type.split('_')[0], + 'PV_ANALYSE': '_'.join(self.analysis_type.split('_')[1:]), + 'PV_RESULTAAT_ID': f"{self.investigation_groups[0]}_{self.effective_stress}_{self.analysis_type}", + 'PV_TYPEVERZAMELING': self.alpha, + 'PV_e_a1_GEM [-]': round(self.e_a1_sutabel, 6) if self.e_a1_sutabel is not None else None, + 'PV_e_a2_GEM [-]': round(self.e_a2_sutabel, 6) if self.e_a2_sutabel is not None else None, + 'PV_svgm_GEM [kPa]': round(self.svgm_gem_sutabel, 6) if self.svgm_gem_sutabel is not None else None, + 'PV_m_GEM [-]': round(self.m_gem_sutabel, 6) if self.m_gem_sutabel is not None else None, + 'PV_a1_KAR [-]': round(self.a1_kar_sutabel, 6) if self.a1_kar_sutabel is not None else None, + 'PV_a2_KAR [-]': round(self.a2_kar_sutabel, 6) if self.a2_kar_sutabel is not None else None, + 'PV_svgm_KAR [kPa]': round(self.svgm_kar_sutabel, 6) if self.svgm_kar_sutabel is not None else None, + 'PV_m_KAR [-]': round(self.m_kar_sutabel, 6) if self.m_kar_sutabel is not None else None, + 'PV_vc_FIT_KAR [-]': round(self.vc_fit_kar_sutabel, 6) if self.vc_fit_kar_sutabel is not None else None, + 'PV_STDEV_LOGN_vc [-]': round(self.STDEV_logn_vc_sutabel, 6) if self.STDEV_logn_vc_sutabel is not None else None, + 'PV_STEYX [-]': round(self.steyx_sutabel, 6) if self.steyx_sutabel is not None else None, + 'PV_VGWNAT_GEM [kN/m3]': round(self.calc_vgwnat_gem, 3) if self.calc_vgwnat_gem is not None else None, + 'PV_VGWNAT_SD [kN/m3]': round(self.calc_vgwnat_sd, 3) if self.calc_vgwnat_sd is not None else None, + 'PV_WATERGEHALTE_GEM': round(self.calc_watergehalte_gem, 3) if self.calc_watergehalte_gem is not None else None, + 'PV_WATERGEHALTE_SD': round(self.calc_watergehalte_sd, 3) if self.calc_watergehalte_sd is not None else None, + 'Timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + + workbook = load_workbook(file_path) + + if 'Resultaten SU-tabel - m' in workbook.sheetnames: + print('Tabblad Resultaten SU-tabel - m in dbase excel bestaat al en wordt aangevuld') + df_existing = read_excel(file_path, sheet_name='Resultaten SU-tabel - m') + df_existing = df_existing.dropna(how='all') + df_existing.columns = df_existing.columns.astype(str) + new_row_df = DataFrame([new_row], columns=df_existing.columns) + df_updated = concat([df_existing, new_row_df], ignore_index=True) + else: + print('Tabblad Resultaten SU-tabel - m in dbase excel bestaat nog niet en wordt aangemaakt') + df_updated = DataFrame([new_row], columns=expected_columns) + + # Ensure all column headers are strings + df_updated.columns = df_updated.columns.astype(str) + + # Write data to Excel + with ExcelWriter(file_path, mode='a', engine='openpyxl', if_sheet_exists='replace') as writer: + df_updated.to_excel(writer, sheet_name='Resultaten SU-tabel - m', index=False) + + # Format the Excel sheet + format_excel_sheet( + file_path=file_path, + sheet_name='Resultaten SU-tabel - m', + num_columns=df_updated.shape[1], + num_rows=df_updated.shape[0], + table_name='ResultatenSUTabelMTable', + index=False + ) + + print(f"Sutabel resultaten toegevoegd aan database: {file_path}") + return df_updated + + +def _create_sutabel_input_table(self: "SUTABEL") -> Table: + """ + Maakt een tabel met de invoerselectie informatie voor sutabel analyse. + + Returns + ------- + Table + ReportLab tabel object met de invoerselectie informatie + """ + if self.sutabel_data_df is None: + return Table([['Geen invoerdata beschikbaar']], hAlign='LEFT') + + columns_base = ['PV_NAAM', 'BORING_POSITIE', 'MONSTER_NIVEAU_NAP_VANAF', 'MONSTER_NIVEAU_NAP_TOT', + 'ANA_TERREINSPANNING', 'ANA_GRENSSPANNING_REKEN', 'ANA_POP_VELD'] + + if self.analysis_type.startswith('TXT'): + columns_extra = ['TXT_SS_VOLUMEGEWICHT_NAT', 'TXT_SS_WATERGEHALTE_VOOR'] + else: + columns_extra = ['DSS_VOLUMEGEWICHT_NAT', 'DSS_WATERGEHALTE_VOOR'] + + available_columns = [col for col in columns_base + columns_extra if col in self.total_sutabel_data_df.columns] + + if not available_columns: + return Table([['Geen invoerdata kolommen beschikbaar']], hAlign='LEFT') + + table_df = self.total_sutabel_data_df[available_columns].copy() + + additional_columns = ['S\'v', 'Su', 'consolidatietype'] + for col in additional_columns: + if col in self.sutabel_data_df.columns and col not in table_df.columns: + # Zorg ervoor dat de indices overeenkomen + if len(table_df) == len(self.sutabel_data_df): + table_df[col] = self.sutabel_data_df[col].values + + column_mapping = { + 'PV_NAAM': 'Groep', + 'BORING_POSITIE': 'Pos.', + 'MONSTER_NIVEAU_NAP_VANAF': 'NAP Van\n[m]', + 'MONSTER_NIVEAU_NAP_TOT': 'NAP Tot\n[m]', + 'TXT_SS_VOLUMEGEWICHT_NAT': 'VGW nat\n[kN/m3]', + 'TXT_SS_VOLUMEGEWICHT_DRG': 'VGW drg\n[kN/m3]', + 'TXT_SS_WATERGEHALTE_VOOR': 'Water\n[%]', + 'DSS_VOLUMEGEWICHT_NAT': 'VGW nat\n[kN/m3]', + 'DSS_VOLUMEGEWICHT_DRG': 'VGW drg\n[kN/m3]', + 'DSS_WATERGEHALTE_VOOR': 'Water\n[%]', + 'S\'v': "σ'v\n[kPa]", + 'Su': "su\n[kPa]", + 'OCR': 'OCR\n[-]', + 'ANA_TERREINSPANNING': 'Terreinspanning\n[kPa]', + 'ANA_GRENSSPANNING_REKEN': 'Grensspanning\n[kPa]', + 'ANA_POP_VELD': 'POP\n[-]' + } + + table_df = table_df.rename(columns={k: v for k, v in column_mapping.items() if k in table_df.columns}) + + for col in table_df.columns: + if table_df[col].dtype in ['float64', 'float32']: + table_df[col] = table_df[col].round(3) + + from reportlab.platypus import Paragraph + header_paragraphs = [] + for col in table_df.columns: + col_display = col.replace('_', '_
') + header_paragraphs.append(Paragraph(f'{col_display}', getSampleStyleSheet()['Normal'])) + + data = table_df.values.tolist() + t_data = [header_paragraphs] + data + + t1 = LongTable(t_data, repeatRows=1, hAlign='LEFT') + t1.setStyle(TableStyle([ + ('ALIGN', (0, 0), (-1, -1), 'LEFT'), + ('GRID', (0, 0), (-1, -1), 0.5, colors.black), + ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'), + ('FONTSIZE', (0, 1), (-1, -1), 7), + ('TOPPADDING', (0, 0), (-1, -1), 2), + ('BOTTOMPADDING', (0, 0), (-1, -1), 2), + ('ROWBACKGROUNDS', (0, 0), (-1, 0), [colors.lightgrey]), + ])) + return t1 + + +def _create_sutabel_parameters_table(self: "SUTABEL") -> Table: + """ + Maakt een tabel met de sutabel-m parameters. + + Returns + ------- + Table + ReportLab tabel object met de sutabel-m parameters + """ + parameters: List[list] = [] + + if hasattr(self, 'e_a1_sutabel') and self.e_a1_sutabel is not None: + parameters.append(['e_a1 (snijpunt gemiddeld) [-]', f"{self.e_a1_sutabel:.6f}"]) + if hasattr(self, 'e_a2_sutabel') and self.e_a2_sutabel is not None: + parameters.append(['e_a2 (helling gemiddeld) [-]', f"{self.e_a2_sutabel:.6f}"]) + if hasattr(self, 'svgm_gem_sutabel') and self.svgm_gem_sutabel is not None: + parameters.append(['svgm_gem [kPa]', f"{self.svgm_gem_sutabel:.6f}"]) + if hasattr(self, 'm_gem_sutabel') and self.m_gem_sutabel is not None: + parameters.append(['m_gem [-]', f"{self.m_gem_sutabel:.6f}"]) + + if hasattr(self, 'a1_kar_sutabel') and self.a1_kar_sutabel is not None: + parameters.append(['a1_kar (snijpunt karakteristiek) [-]', f"{self.a1_kar_sutabel:.6f}"]) + if hasattr(self, 'a2_kar_sutabel') and self.a2_kar_sutabel is not None: + parameters.append(['a2_kar (helling karakteristiek) [-]', f"{self.a2_kar_sutabel:.6f}"]) + if hasattr(self, 'svgm_kar_sutabel') and self.svgm_kar_sutabel is not None: + parameters.append(['svgm_kar [kPa]', f"{self.svgm_kar_sutabel:.6f}"]) + if hasattr(self, 'm_kar_sutabel') and self.m_kar_sutabel is not None: + parameters.append(['m_kar [-]', f"{self.m_kar_sutabel:.6f}"]) + + if hasattr(self, 'vc_fit_kar_sutabel') and self.vc_fit_kar_sutabel is not None: + parameters.append(['vc_fit_kar [-]', f"{self.vc_fit_kar_sutabel:.6f}"]) + if hasattr(self, 'STDEV_logn_vc_sutabel') and self.STDEV_logn_vc_sutabel is not None: + parameters.append(['STDEV lognormaal [-]', f"{self.STDEV_logn_vc_sutabel:.6f}"]) + if hasattr(self, 'steyx_sutabel') and self.steyx_sutabel is not None: + parameters.append(['STEYX [-]', f"{self.steyx_sutabel:.6f}"]) + + parameters.append(['Type verzameling: lokaal = 1.0; regionaal = 0.75', f"{self.alpha:.2f}"]) + + if hasattr(self, 'calc_vgwnat_gem') and self.calc_vgwnat_gem is not None: + parameters.append(['VGW nat gemiddeld [kN/m3]', f"{self.calc_vgwnat_gem:.3f}"]) + if hasattr(self, 'calc_watergehalte_gem') and self.calc_watergehalte_gem is not None: + parameters.append(['Watergehalte gemiddeld', f"{self.calc_watergehalte_gem:.3f}"]) + + t = Table([['Parameter', 'Waarde']] + parameters, hAlign='LEFT') + t.setStyle(TableStyle([ + ('ALIGN', (0, 0), (-1, -1), 'LEFT'), + ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey), + ('GRID', (0, 0), (-1, -1), 0.5, colors.black), + ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), + ('FONTSIZE', (0, 0), (-1, 0), 10), + ('FONTSIZE', (0, 1), (-1, -1), 9), + ])) + return t + + +def _create_sutabel_grafiek_table(self: "SUTABEL") -> Table: + """ + Maakt een tabel met de sutabel grafiek data (su_gem en su_kar lijnen). + + Returns + ------- + Table + ReportLab tabel object met de sutabel grafiek data + """ + if self.sutabel_grafiek is None: + return Table([['Geen sutabel grafiek data beschikbaar']], hAlign='LEFT') + + df = self.sutabel_grafiek.copy() + for col in df.columns: + if df[col].dtype in ['float64', 'float32']: + df[col] = df[col].round(3) + + header = df.columns.tolist() + data = df.values.tolist() + t_data = [header] + data + + t = Table(t_data, hAlign='LEFT') + t.setStyle(TableStyle([ + ('ALIGN', (0, 0), (-1, -1), 'LEFT'), + ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey), + ('GRID', (0, 0), (-1, -1), 0.5, colors.black), + ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), + ('FONTSIZE', (0, 0), (-1, 0), 10), + ('FONTSIZE', (0, 1), (-1, -1), 9), + ])) + return t + + +def _create_su_fit_vc_table(self: "SUTABEL") -> Table: + """ + Maakt een tabel met de su fit constante vc data. + + Returns + ------- + Table + ReportLab tabel object met de su fit vc data + """ + if self.su_fit_constante_vc is None: + return Table([['Geen vc fit data beschikbaar (vc_fit_kar niet opgegeven)']], hAlign='LEFT') + + df = self.su_fit_constante_vc.copy() + for col in df.columns: + if df[col].dtype in ['float64', 'float32']: + df[col] = df[col].round(3) + + header = df.columns.tolist() + data = df.values.tolist() + t_data = [header] + data + + t = Table(t_data, hAlign='LEFT') + t.setStyle(TableStyle([ + ('ALIGN', (0, 0), (-1, -1), 'LEFT'), + ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey), + ('GRID', (0, 0), (-1, -1), 0.5, colors.black), + ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), + ('FONTSIZE', (0, 0), (-1, 0), 10), + ('FONTSIZE', (0, 1), (-1, -1), 9), + ])) + return t + + +def save_sutabel_to_pdf(self: "SUTABEL", path: str, vc_fit_kar: float = None) -> str: + """ + Slaat de sutabel-m analyseresultaten op in een PDF-document. + + De PDF bevat: + - Titel met analysedetails + - Beide overzichtsfiguren (ln(s'v)-ln(su) en s'v-su) + - Tabel met parameters + - Tabel met sutabel grafiek data (su_gem en su_kar) + - Tabel met su fit constante vc data (indien van toepassing) + - Tabel met invoerselectie informatie + + Parameters + ---------- + path : str + Map locatie waar het PDF-bestand moet worden opgeslagen + vc_fit_kar : float, optioneel + Coefficient of Variation voor de fit + + Returns + ------- + str + Het absolute bestandspad van het aangemaakte PDF-bestand + """ + title = f"Sutabel-m analyse met {self.effective_stress} op {self.investigation_groups[0]}" + file_name = f"sutabel_pdf_export_{self.investigation_groups[0]}_{self.analysis_type}_{str(self.effective_stress).replace('%', 'procent_').replace(' ', '')}.pdf" + file_path = f"{path}/{file_name}" + + self._run_sutabel() + if vc_fit_kar is not None: + self.get_sutabel_parameters(vc_fit_kar_sutabel=vc_fit_kar) + elif hasattr(self, 'vc_fit_kar_sutabel') and self.vc_fit_kar_sutabel is not None: + self.get_sutabel_parameters(vc_fit_kar_sutabel=self.vc_fit_kar_sutabel) + else: + self.get_sutabel_parameters() + + if self.sutabel_grafiek is None: + self.calculate_sutabel_grafiek() + + doc = SimpleDocTemplate(file_path, pagesize=landscape(A4)) + styles = getSampleStyleSheet() + + if 'Left' not in styles: + styles.add(ParagraphStyle(name='Left', parent=styles['Normal'], alignment=TA_LEFT)) + if 'TitleLeft' not in styles: + styles.add(ParagraphStyle(name='TitleLeft', parent=styles['Title'], alignment=TA_LEFT)) + if 'Heading3' not in styles: + styles.add(ParagraphStyle(name='Heading3', parent=styles['Heading2'], alignment=TA_LEFT)) + + story = [] + + story.append(Paragraph(title, styles['TitleLeft'])) + story.append(Spacer(width=1, height=12)) + + if not PIL_AVAILABLE: + story.append(Paragraph("Figuren kunnen niet worden toegevoegd: PIL (Pillow) niet beschikbaar", styles['Normal'])) + story.append(Spacer(width=1, height=12)) + else: + try: + fig_path1 = f"{path}/temp_sutabel_plot1.png" + self.show_title = False + + self.figure = go.Figure() + self.set_figure_sv_su_sutabel() + + if hasattr(self, 'figure') and self.figure is not None: + fig_width = 1280 + fig_height = 720 + self.figure.write_image(fig_path1, width=fig_width, height=fig_height, scale=4, format="png") + + title_and_heading_height = 100 + available_height = doc.height - title_and_heading_height + + with PILImage.open(fig_path1) as im: + img_width_px, img_height_px = im.size + + max_width_pt = doc.width * 0.95 + aspect = img_height_px / img_width_px + + img_width_pt = min(max_width_pt, doc.width) + img_height_pt = img_width_pt * aspect + + if img_height_pt > available_height: + img_height_pt = available_height * 0.95 + img_width_pt = img_height_pt / aspect + + # Maak ReportLab Image aan + img1 = RLImage(fig_path1) + img1.drawWidth = img_width_pt + img1.drawHeight = img_height_pt + img1.hAlign = 'LEFT' + + story.append(Paragraph("Figuur 1: ln(s'v) vs ln(su)", styles['Heading2'])) + story.append(Spacer(width=1, height=6)) + story.append(img1) + story.append(PageBreak()) + else: + story.append(Paragraph("Figuur 1 kon niet worden gegenereerd", styles['Normal'])) + story.append(Spacer(width=1, height=12)) + except Exception as e: + story.append(Paragraph(f"Fout bij genereren Figuur 1: {str(e)}", styles['Normal'])) + story.append(Spacer(width=1, height=12)) + + try: + fig_path2 = f"{path}/temp_sutabel_plot2.png" + self.show_title = False + + self.figure = go.Figure() + self.set_figure_ln_sv_ln_su_sutabel() + + if hasattr(self, 'figure') and self.figure is not None: + fig_width = 1280 + fig_height = 720 + self.figure.write_image(fig_path2, width=fig_width, height=fig_height, scale=4, format="png") + + with PILImage.open(fig_path2) as im: + img_width_px, img_height_px = im.size + + max_width_pt = doc.width * 0.95 + aspect = img_height_px / img_width_px + + img_width_pt = min(max_width_pt, doc.width) + img_height_pt = img_width_pt * aspect + + available_height = doc.height - 50 + + if img_height_pt > available_height: + img_height_pt = available_height * 0.95 + img_width_pt = img_height_pt / aspect + + img2 = RLImage(fig_path2) + img2.drawWidth = img_width_pt + img2.drawHeight = img_height_pt + img2.hAlign = 'LEFT' + + story.append(Paragraph("Figuur 2: s'v vs su", styles['Heading2'])) + story.append(Spacer(width=1, height=6)) + story.append(img2) + story.append(PageBreak()) + else: + story.append(Paragraph("Figuur 2 kon niet worden gegenereerd", styles['Normal'])) + story.append(Spacer(width=1, height=12)) + except Exception as e: + story.append(Paragraph(f"Fout bij genereren Figuur 2: {str(e)}", styles['Normal'])) + story.append(Spacer(width=1, height=12)) + + # Voeg parameters tabel toe + story.append(Paragraph("Parameters", styles['Heading2'])) + story.append(Spacer(width=1, height=6)) + story.append(_create_sutabel_parameters_table(self)) + story.append(Spacer(width=1, height=12)) + + # Voeg sutabel grafiek data toe + story.append(Paragraph("Sutabel grafiek data (su_gem en su_kar lijnen)", styles['Heading2'])) + story.append(Spacer(width=1, height=6)) + story.append(_create_sutabel_grafiek_table(self)) + story.append(Spacer(width=1, height=12)) + + # Voeg su fit vc data toe (indien beschikbaar) + if self.su_fit_constante_vc is not None: + story.append(Paragraph("Su fit met constante vc data", styles['Heading2'])) + story.append(Spacer(width=1, height=6)) + story.append(_create_su_fit_vc_table(self)) + story.append(Spacer(width=1, height=12)) + + story.append(PageBreak()) + story.append(Paragraph("Invoerselectie", styles['Heading2'])) + story.append(Spacer(width=1, height=6)) + story.append(_create_sutabel_input_table(self)) + + try: + doc.build(story) + print(f"Sutabel PDF export voltooid: {file_path}") + return file_path + except Exception as e: + print(f"Fout bij maken PDF: {e}") + raise + diff --git a/pv_tool/sutabel_analysis/sutabel_analysis.py b/pv_tool/sutabel_analysis/sutabel_analysis.py new file mode 100644 index 0000000..c4f6ce0 --- /dev/null +++ b/pv_tool/sutabel_analysis/sutabel_analysis.py @@ -0,0 +1,630 @@ +""" +SUTABEL Analysis Class + +Deze module bevat de SUTABEL klasse voor het uitvoeren van sutabel-m analyses. +De sutabel-m methode analyseert overgeconsolideerde (OC) triaxiaal of DSS proeven +om de ongedraineerde schuifsterkte te bepalen. +""" + +import math +from pv_tool.imports.import_data import Dbase +from typing import Optional, List, Literal +from pv_tool.shansep_analysis.globals import (TEXTUAL_NAMES, NEW_COLUMN_NAMES, TEXTUAL_NAMES_DSS) +from pandas import DataFrame, ExcelWriter +import plotly.graph_objects as go +from pv_tool.shansep_analysis.calc_parameters import ( + calc_watergehalte_gem_txt, calc_watergehalte_gem_dss, + calc_watergehalte_sd_txt, calc_watergehalte_sd_dss, + calc_vgwnat_gem_txt, calc_vgwnat_gem_dss, + calc_vgwnat_sd_txt, calc_vgwnat_sd_dss +) +from pandas import read_excel + + +class SUTABEL: + """ + Klasse voor het uitvoeren van sutabel-m analyses. + + De sutabel-m methode analyseert overgeconsolideerde (OC) triaxiaal of DSS proeven + om parameters te bepalen voor het berekenen van ongedraineerde schuifsterkte. + + Attributes + ---------- + dbase : Dbase + Database object met proefgegevens + analysis_type : str + Type analyse ('TXT_su_tabel' of 'DSS_su_tabel') + investigation_groups : List[str] + Lijst met te analyseren proevenverzamelingen + effective_stress : str + Effectieve spanning niveau (bijv. '15% rek') + alpha : float + Type verzameling (1.0 = lokaal, 0.75 = regionaal) + + Parameters (berekend) + ---------- + e_a1_sutabel : float + Snijpunt gemiddeld in ln-ruimte + e_a2_sutabel : float + Helling gemiddeld in ln-ruimte + svgm_gem_sutabel : float + exp(e_a1) - sutabel parameter gemiddeld [kPa] + m_gem_sutabel : float + 1 - e_a2 - exponent parameter gemiddeld + a1_kar_sutabel : float + Snijpunt karakteristiek in ln-ruimte + a2_kar_sutabel : float + Helling karakteristiek in ln-ruimte + svgm_kar_sutabel : float + exp(a1_kar) - sutabel parameter karakteristiek [kPa] + m_kar_sutabel : float + 1 - a2_kar - exponent parameter karakteristiek + vc_fit_kar_sutabel : float + Coefficient of Variation voor fit (user input) + STDEV_logn_vc_sutabel : float + sqrt(LN(1 + vc^2)) - standaarddeviatie lognormaal + steyx_sutabel : float + Standaardfout van de schatting + """ + + def __init__(self, + dbase: Dbase, + analysis_type: Literal['TXT_su_tabel', 'DSS_su_tabel'], + investigation_groups: List[str], + effective_stress: str): + """ + Initialiseert een SUTABEL analyse instantie. + + Parameters + ---------- + dbase : Dbase + Database object met proefgegevens + analysis_type : Literal['TXT_su_tabel', 'DSS_su_tabel'] + Type analyse + investigation_groups : List[str] + Lijst met te analyseren proevenverzamelingen + effective_stress : str + Effectieve spanning niveau (bijv. '15% rek') + alpha : float, optional + Type verzameling (1.0 = lokaal, 0.75 = regionaal), default 0.75 + """ + self.dbase = dbase + self.dbase_df = dbase.dbase_df + self.analysis_type = analysis_type + self.investigation_groups = investigation_groups + self.effective_stress = effective_stress + self.alpha = 0.75 + self.show_title = True + + # Berekende watergehalte en volumegewicht + self.calc_watergehalte_gem: Optional[float] = None + self.calc_watergehalte_sd: Optional[float] = None + self.calc_vgwnat_gem: Optional[float] = None + self.calc_vgwnat_sd: Optional[float] = None + + # Sutabel basis parameters + self.e_a2_sutabel: Optional[float] = None + self.e_a1_sutabel: Optional[float] = None + self.a2_kar_sutabel: Optional[float] = None + self.a1_kar_sutabel: Optional[float] = None + self.steyx_sutabel: Optional[float] = None + + # Sutabel afgeleide parameters voor grafieken + self.svgm_gem_sutabel: Optional[float] = None # exp(e_a1) + self.m_gem_sutabel: Optional[float] = None # 1 - e_a2 + self.svgm_kar_sutabel: Optional[float] = None # exp(a1_kar) + self.m_kar_sutabel: Optional[float] = None # 1 - a2_kar + self.vc_fit_kar_sutabel: Optional[float] = None # User input + self.STDEV_logn_vc_sutabel: Optional[float] = None # sqrt(LN(1 + vc^2)) + + # Handmatige parameters + self.parameters_handmatig: bool = False + self.a1_kar_handmatig: Optional[float] = None + self.a2_kar_handmatig: Optional[float] = None + self.vc_fit_kar_handmatig: Optional[float] = None + + # Dataframes + self.sutabel_data_df: Optional[DataFrame] = None + self.total_sutabel_data_df: Optional[DataFrame] = None + self.sutabel_filtered_data_df: Optional[DataFrame] = None + self.sutabel_grafiek: Optional[DataFrame] = None + self.su_fit_constante_vc: Optional[DataFrame] = None + + # Figure voor plotly + self.figure: Optional[go.Figure] = None + + # ========= Instelling en Data Ophalen Methodes ========== + + def apply_settings(self, alpha: Optional[float] = None): + """Met deze functie kan je de alpha en materiaalfactoren opgeven.""" + self.alpha = alpha if alpha is not None else self.alpha + + def get_sutabel_data(self): + """ + Haalt de relevante proefgegevens op uit de database. + + Filtert de database op basis van: + - Analysis type (TXT of DSS) + - Investigation groups (PV_NAAM) + - Effective stress niveau + + Maakt self.sutabel_data_df en self.total_sutabel_data_df aan. + """ + if self.analysis_type in ['TXT_su_tabel']: + self.sutabel_data_df = self.dbase_df[self.dbase_df['ALG__TRIAXIAAL']].copy() + self.sutabel_data_df = self.sutabel_data_df[self.sutabel_data_df['PV_NAAM'].isin( + self.investigation_groups)].copy() + self.calc_watergehalte_gem = calc_watergehalte_gem_txt(self.sutabel_data_df) + self.calc_watergehalte_sd = calc_watergehalte_sd_txt(self.sutabel_data_df) + self.calc_vgwnat_gem = calc_vgwnat_gem_txt(self.sutabel_data_df) + self.calc_vgwnat_sd = calc_vgwnat_sd_txt(self.sutabel_data_df) + self.total_sutabel_data_df = self.sutabel_data_df.copy() + self.sutabel_data_df = self.sutabel_data_df[TEXTUAL_NAMES.get(self.effective_stress, [])].copy() + + elif self.analysis_type in ['DSS_su_tabel']: + self.sutabel_data_df = self.dbase_df[self.dbase_df['ALG__DSS']].copy() + self.sutabel_data_df = self.sutabel_data_df[self.sutabel_data_df['PV_NAAM'].isin( + self.investigation_groups)].copy() + self.calc_watergehalte_gem = calc_watergehalte_gem_dss(self.sutabel_data_df) + self.calc_watergehalte_sd = calc_watergehalte_sd_dss(self.sutabel_data_df) + self.calc_vgwnat_gem = calc_vgwnat_gem_dss(self.sutabel_data_df) + self.calc_vgwnat_sd = calc_vgwnat_sd_dss(self.sutabel_data_df) + self.total_sutabel_data_df = self.sutabel_data_df.copy() + self.sutabel_data_df = self.sutabel_data_df[TEXTUAL_NAMES_DSS.get(self.effective_stress, [])].copy() + + self.sutabel_data_df.columns = NEW_COLUMN_NAMES + + def get_previous_results(self, path: str, file_name: str): + """ + Zoekt naar eerdere analyseresultaten in een Excel-bestand. + + Parameters + ---------- + path: str + Pad naar de map waar het Excel-bestand staat + file_name: str + Naam van het Excel-bestand + + Returns + ------- + DataFrame of None + DataFrame met eerdere resultaten als deze gevonden zijn, anders None + """ + + file_path = f"{path}/{file_name}" + + try: + with open(file_path, 'r'): + pass + except FileNotFoundError: + raise FileNotFoundError(f"Er is geen dbase aanwezig op de locatie {file_path}.") + + try: + results_df = read_excel(file_path, sheet_name='Resultaten') + except ValueError: + print("Er is geen tabblad 'Resultaten' aanwezig in het Excel-bestand.") + return None + + filtered_df = results_df[ + (results_df['PV_RESULTAAT_ID'].str.contains(self.investigation_groups[0])) & + (results_df['PV_RESULTAAT_ID'].str.contains(self.effective_stress)) & + (results_df['PV_RESULTAAT_ID'].str.contains(self.analysis_type)) + ] + + if filtered_df.empty: + print("Er zijn geen eerdere resultaten gevonden voor de opgegeven parameters.") + return None + + latest_entry = filtered_df.sort_values(by='Timestamp', ascending=False).iloc[0] + + return latest_entry + + def expand_analysis_df_sutabel(self): + """ + Berekent alle benodigde parameters per monster voor de sutabel-m analyse. + + Filtert op OC proeven en maakt kolommen aan voor: + - ln(s'v) en ln(su) + - Regressie componenten (s_tt, s_ty, chi_2) + - Effectieve spanning waarden + - 5% boven- en ondergrenzen + - Ondergrens parameters + """ + from pv_tool.sutabel_analysis.expand_analysis import ( + calculate_ln_sv_sutabel, calculate_ln_su_sutabel, + calculate_sv_tt_sutabel, calculate_sv_ty_sutabel, + calculate_chi_2_sutabel, calculate_sv_eff_sutabel, + calculate_5pr_ondergrens_sutabel, calculate_5pr_bovengrens_sutabel, + calculate_sv_tt_ondergrens_sutabel, calculate_sv_ty_ondergrens_sutabel, + calculate_chi_2_ondergrens_sutabel + ) + + # Filter op alleen OC proeven + self.sutabel_filtered_data_df = self.sutabel_data_df[ + self.sutabel_data_df['consolidatietype'] == 'OC' + ].copy() + + # Update sutabel_data_df to work with the OC-filtered data + self.sutabel_data_df = self.sutabel_filtered_data_df.copy() + + # Bereken ln(s'v) en ln(su) kolommen + calculate_ln_sv_sutabel(self) + calculate_ln_su_sutabel(self) + + # Bereken s_tt en s_ty voor lineaire regressie + calculate_sv_tt_sutabel(self) + calculate_sv_ty_sutabel(self) + + # Bereken chi_2 + calculate_chi_2_sutabel(self) + + # Bereken effectieve spanning waarden + calculate_sv_eff_sutabel(self) + + # Bereken 5% onder- en bovengrenzen + calculate_5pr_ondergrens_sutabel(self) + calculate_5pr_bovengrens_sutabel(self) + + # Bereken s_tt, s_ty en chi_2 voor ondergrenzen + calculate_sv_tt_ondergrens_sutabel(self) + calculate_sv_ty_ondergrens_sutabel(self) + calculate_chi_2_ondergrens_sutabel(self) + + def get_sutabel_parameters(self, vc_fit_kar_sutabel: Optional[float] = None): + """ + Berekent de sutabel-m parameters. + + Parameters + ---------- + vc_fit_kar_sutabel : float, optioneel + Coefficient of Variation voor sutabel fit (user input) + """ + from pv_tool.sutabel_analysis.variables import ( + e_a2_sutabel, e_a1_sutabel, + a2_kar_sutabel, a1_kar_sutabel, + steyx_sutabel + ) + + self.e_a2_sutabel = e_a2_sutabel(self) + self.e_a1_sutabel = e_a1_sutabel(self) + self.a2_kar_sutabel = a2_kar_sutabel(self) + self.a1_kar_sutabel = a1_kar_sutabel(self) + self.steyx_sutabel = steyx_sutabel(self) + + # Bereken afgeleide parameters voor grafiek + self.svgm_gem_sutabel = math.exp(self.e_a1_sutabel) + self.m_gem_sutabel = 1 - self.e_a2_sutabel + self.svgm_kar_sutabel = math.exp(self.a1_kar_sutabel) + self.m_kar_sutabel = 1 - self.a2_kar_sutabel + + # Sla vc_fit_kar op (kan None zijn als niet opgegeven) + if vc_fit_kar_sutabel is not None: + self.vc_fit_kar_sutabel = vc_fit_kar_sutabel + self.STDEV_logn_vc_sutabel = math.sqrt(math.log(1 + (self.vc_fit_kar_sutabel ** 2))) + else: + self.vc_fit_kar_sutabel = None + self.STDEV_logn_vc_sutabel = None + + def calculate_sutabel_grafiek(self): + """ + Berekent de dataframes voor sutabel grafiek lijnen. + + Maakt twee dataframes: + - sutabel_grafiek: bevat su_gem en su_kar lijnen + - su_fit_constante_vc: bevat su_kar fit met constante vc lijn + """ + import numpy as np + from scipy.stats import lognorm + + # Bepaal s'v waarden voor de grafiek + max_sv = self.sutabel_data_df['S\'v'].max() + sv_values = [1, 5, 10, 20, 30, 40, max_sv] + + # Bereken su_gem en su_kar + su_gem_values = [self.svgm_gem_sutabel * (sv ** (1 - self.m_gem_sutabel)) for sv in sv_values] + su_kar_values = [self.svgm_kar_sutabel * (sv ** (1 - self.m_kar_sutabel)) for sv in sv_values] + + # Maak sutabel_grafiek dataframe + self.sutabel_grafiek = DataFrame({ + "s'v [kPa]": sv_values, + "su_gem [kPa]": su_gem_values, + "su_kar [kPa]": su_kar_values + }) + + # Als vc_fit_kar is opgegeven, bereken ook de constante vc fit + if self.vc_fit_kar_sutabel is not None and self.STDEV_logn_vc_sutabel is not None: + # Bereken ln waarden + ln_su_gem = [math.log(su) - 0.5 * (self.STDEV_logn_vc_sutabel ** 2) for su in su_gem_values] + ln_su_kar = [math.log(su) - 0.5 * (self.STDEV_logn_vc_sutabel ** 2) for su in su_kar_values] + + # Bereken su_kar fit met constante vc + su_kar_fit_vc = [lognorm.ppf(0.05, s=self.STDEV_logn_vc_sutabel, scale=math.exp(ln)) + for ln in ln_su_gem] + + self.su_fit_constante_vc = DataFrame({ + "s'v [kPa]": sv_values, + "ln(su_gem) [kPa]": ln_su_gem, + "ln(su_kar) [kPa]": ln_su_kar, + "su_kar fit met constante vc [kPa]": su_kar_fit_vc + }) + else: + self.su_fit_constante_vc = None + + def _run_sutabel(self): + """ + Voert de volledige sutabel analyse uit in de juiste volgorde. + """ + self.get_sutabel_data() + self.expand_analysis_df_sutabel() + self.get_sutabel_parameters() + + # ========== Handmatige Parameters en wegschrijven ========== + + def set_manual_parameters(self, + a1_kar: Optional[float] = None, + a2_kar: Optional[float] = None, + vc_fit_kar: Optional[float] = None): + """ + Stelt handmatige parameters in voor de sutabel analyse. + + Na het instellen van handmatige parameters worden deze gebruikt in plaats van + de berekende parameters. Dit is handig voor iteratieve analyses waarbij de + gebruiker verschillende parameterwaarden wil testen. + + De analyse wordt automatisch uitgevoerd als deze nog niet is gedaan. + + Parameters + ---------- + a1_kar : float, optioneel + Handmatig ingesteld karakteristiek snijpunt in ln-ruimte + a2_kar : float, optioneel + Handmatig ingestelde karakteristieke helling in ln-ruimte + vc_fit_kar : float, optioneel + Handmatig ingestelde Coefficient of Variation voor fit + + Examples + -------- + >>> sutabel = SUTABEL(...) + >>> sutabel.show_figure_sv_su_sutabel() # Automatisch analyse + >>> + >>> # Pas parameters aan en analyseer opnieuw + >>> sutabel.set_manual_parameters(a1_kar=0.85, a2_kar=0.70, vc_fit_kar=0.25) + >>> sutabel.show_figure_sv_su_sutabel() # Gebruikt nu handmatige parameters + """ + # Zorg dat analyse is uitgevoerd voordat handmatige parameters worden ingesteld + if self.sutabel_data_df is None: + self._run_sutabel() + + self.parameters_handmatig = True + + # Sla handmatige parameters op + if a1_kar is not None: + self.a1_kar_handmatig = a1_kar + if a2_kar is not None: + self.a2_kar_handmatig = a2_kar + if vc_fit_kar is not None: + self.vc_fit_kar_handmatig = vc_fit_kar + + # Herbereken de afgeleide parameters met de nieuwe waarden + self._update_parameters_from_manual() + + def _update_parameters_from_manual(self): + """ + Update de gebruikte parameters op basis van handmatige input. + + Als handmatige parameters zijn ingesteld, worden deze gebruikt. + Anders blijven de berekende parameters behouden. + """ + if not self.parameters_handmatig: + return + + # Gebruik handmatige a1_kar of behoud berekende waarde + if self.a1_kar_handmatig is not None: + a1_kar_te_gebruiken = self.a1_kar_handmatig + else: + a1_kar_te_gebruiken = self.a1_kar_sutabel + + # Gebruik handmatige a2_kar of behoud berekende waarde + if self.a2_kar_handmatig is not None: + a2_kar_te_gebruiken = self.a2_kar_handmatig + else: + a2_kar_te_gebruiken = self.a2_kar_sutabel + + # Gebruik handmatige vc_fit_kar of behoud berekende waarde + if self.vc_fit_kar_handmatig is not None: + vc_te_gebruiken = self.vc_fit_kar_handmatig + else: + vc_te_gebruiken = self.vc_fit_kar_sutabel + + # Bereken afgeleide parameters met de te gebruiken waarden + self.svgm_kar_sutabel = math.exp(a1_kar_te_gebruiken) + self.m_kar_sutabel = 1 - a2_kar_te_gebruiken + + # Update vc parameters + if vc_te_gebruiken is not None: + self.vc_fit_kar_sutabel = vc_te_gebruiken + self.STDEV_logn_vc_sutabel = math.sqrt(math.log(1 + (vc_te_gebruiken ** 2))) + + # Herbereken de grafiek dataframes met de nieuwe parameters + self.calculate_sutabel_grafiek() + + def write_analysis_to_excel(self, file_path: str): + """ + Schrijft de analyse dataframes naar een Excel-bestand. + + Parameters + ---------- + file_path : str + Pad naar de map waar het Excel-bestand moet worden opgeslagen plus de bestandsnaam + """ + with ExcelWriter(file_path) as writer: + if self.sutabel_data_df is not None: + self.sutabel_data_df.to_excel(writer, sheet_name='Data', index=False) + if self.sutabel_filtered_data_df is not None: + self.sutabel_filtered_data_df.to_excel(writer, sheet_name='Sutabel Filtered Data', index=False) + if self.sutabel_grafiek is not None: + self.sutabel_grafiek.to_excel(writer, sheet_name='Sutabel Grafiek', index=False) + if self.su_fit_constante_vc is not None: + self.su_fit_constante_vc.to_excel(writer, sheet_name='vc Fit', index=False) + + # ========== Visualisatie Methodes ========== + + def set_figure_ln_sv_ln_su_sutabel(self, plot_extra_dataset: Optional[List] = None): + """ + Maakt een visualisatie van de sutabel analyseresultaten voor ln(s'v) vs ln(su). + + Deze plot toont: + - Proefresultaten (OC data) + - Lineaire fit + - 5% boven- en ondergrens + - Fysische realiseerbare ondergrens (gebaseerd op a1_kar en a2_kar) + + De analyse wordt automatisch uitgevoerd als deze nog niet is gedaan. + """ + from pv_tool.sutabel_analysis.visualization_sutabel import ( + add_proefresultaten_ln_sv_ln_su_sutabel, + add_extra_proefresultaten_ln_sv_ln_su_sutabel, + add_lineair_fit_ln_sv_ln_su_sutabel, + add_5pr_bovengrens_ln_sv_ln_su_sutabel, + add_5pr_ondergrens_ln_sv_ln_su_sutabel, + add_fysische_realiseerbare_ondergrens_ln_sv_ln_su_sutabel, + set_layout_ln_sv_ln_su_sutabel + ) + + # Voer analyse uit als nog niet gedaan + if self.sutabel_data_df is None: + self._run_sutabel() + + if plot_extra_dataset is not None: + add_extra_proefresultaten_ln_sv_ln_su_sutabel(self, plot_extra_dataset) + add_proefresultaten_ln_sv_ln_su_sutabel(self) + add_lineair_fit_ln_sv_ln_su_sutabel(self) + add_5pr_bovengrens_ln_sv_ln_su_sutabel(self) + add_5pr_ondergrens_ln_sv_ln_su_sutabel(self) + add_fysische_realiseerbare_ondergrens_ln_sv_ln_su_sutabel(self) + set_layout_ln_sv_ln_su_sutabel(self) + + def show_figure_ln_sv_ln_su_sutabel(self, plot_extra_dataset: Optional[List] = None): + """ + Toont de visualisatie van de sutabel analyseresultaten voor ln(s'v) vs ln(su). + + De analyse wordt automatisch uitgevoerd als deze nog niet is gedaan. + """ + self.figure = go.Figure() + self.set_figure_ln_sv_ln_su_sutabel(plot_extra_dataset) + self.figure.show() + + def set_figure_sv_su_sutabel(self, plot_extra_dataset: Optional[List] = None): + """ + Maakt een visualisatie van de sutabel analyseresultaten voor s'v vs su. + + Deze plot toont: + - Proefresultaten (OC data) + - Sutabel_gem lijn + - Sutabel_kar lijn + - Su_kar fit met constante VC (als vc_fit_kar is opgegeven) + + Als handmatige parameters zijn ingesteld, worden deze gebruikt voor de visualisatie. + De analyse wordt automatisch uitgevoerd als deze nog niet is gedaan. + """ + from pv_tool.sutabel_analysis.visualization_sutabel import ( + add_proefresultaten_sv_su_sutabel, + add_extra_proefresultaten_sv_su_sutabel, + add_sutabel_gem_line, + add_sutabel_kar_line, + add_su_kar_fit_constante_vc, + set_layout_sv_su_sutabel + ) + + # Voer analyse uit als nog niet gedaan + if self.sutabel_data_df is None: + self._run_sutabel() + + # Update parameters als handmatige waarden zijn ingesteld + if self.parameters_handmatig: + self._update_parameters_from_manual() + + # Bereken grafiek dataframes + self.calculate_sutabel_grafiek() + + # Voeg data en lijnen toe + if plot_extra_dataset is not None: + add_extra_proefresultaten_sv_su_sutabel(self, plot_extra_dataset) + add_proefresultaten_sv_su_sutabel(self) + add_sutabel_gem_line(self) + add_sutabel_kar_line(self) + + # Voeg vc fit lijn toe als deze beschikbaar is + if self.su_fit_constante_vc is not None: + add_su_kar_fit_constante_vc(self) + + set_layout_sv_su_sutabel(self) + + def show_figure_sv_su_sutabel(self, plot_extra_dataset: Optional[List] = None): + """ + Toont de visualisatie van de sutabel analyseresultaten voor s'v vs su. + + De analyse wordt automatisch uitgevoerd als deze nog niet is gedaan. + """ + self.figure = go.Figure() + self.set_figure_sv_su_sutabel(plot_extra_dataset) + self.figure.show() + + # ========== Export Methodes ========== + + def add_results_to_dbase(self, path: str, file_name: str = 'Template_PVtool5_0.xlsx'): + """ + Voegt de sutabel-m analyseresultaten toe aan de database Excel-bestand. + + De analyse wordt automatisch uitgevoerd als deze nog niet is gedaan. + + Parameters + ---------- + path : str + Pad naar de map waar het Excel-bestand staat + file_name : str + Naam van het Excel-bestand + + Returns + ------- + DataFrame + Bijgewerkte DataFrame met alle resultaten + """ + # Voer analyse uit als nog niet gedaan + if self.sutabel_data_df is None or self.e_a1_sutabel is None: + self._run_sutabel() + # Zorg dat parameters zijn berekend + if self.e_a1_sutabel is None: + self.get_sutabel_parameters() + + from pv_tool.sutabel_analysis.save_and_export import add_sutabel_results_to_dbase + return add_sutabel_results_to_dbase(self, path, file_name) + + def save_to_pdf(self, path: str, vc_fit_kar: float = None) -> str: + """ + Slaat de sutabel-m analyseresultaten op in een PDF-document. + + De analyse wordt automatisch uitgevoerd als deze nog niet is gedaan. + + Parameters + ---------- + path : str + Map locatie waar het PDF-bestand moet worden opgeslagen + vc_fit_kar : float, optioneel + Coefficient of Variation voor de fit + + Returns + ------- + str + Het absolute bestandspad van het aangemaakte PDF-bestand + """ + # Voer analyse uit als nog niet gedaan + if self.sutabel_data_df is None or self.e_a1_sutabel is None: + self._run_sutabel() + # Zorg dat parameters zijn berekend + if self.e_a1_sutabel is None: + self.get_sutabel_parameters(vc_fit_kar_sutabel=vc_fit_kar) + + from pv_tool.sutabel_analysis.save_and_export import save_sutabel_to_pdf + return save_sutabel_to_pdf(self, path, vc_fit_kar) + diff --git a/pv_tool/sutabel_analysis/variables.py b/pv_tool/sutabel_analysis/variables.py new file mode 100644 index 0000000..ad37fa0 --- /dev/null +++ b/pv_tool/sutabel_analysis/variables.py @@ -0,0 +1,141 @@ +""" +Variabelen en parameters voor Sutabel-m analyse. + +Deze module bevat alle functies voor het berekenen van statistische parameters +en variabelen voor sutabel-m analyses. +""" + +from typing import TYPE_CHECKING +import numpy as np +from scipy.stats import t + +if TYPE_CHECKING: + from pv_tool.sutabel_analysis.sutabel_analysis import SUTABEL + + +# -------------------------- sutabel-m methode --------------------------------------- + +def count_ln_sv_sutabel(self: "SUTABEL"): + """Telt het aantal ln(s'v) waarden in de sutabel dataframe.""" + return self.sutabel_data_df['ln(s\'v)'].count() + + +def sum_ln_sv_sutabel(self: "SUTABEL"): + """Berekent de som van ln(s'v) waarden in de sutabel dataframe.""" + return self.sutabel_data_df['ln(s\'v)'].sum() + + +def sum_ln_su_sutabel(self: "SUTABEL"): + """Berekent de som van ln(su) waarden in de sutabel dataframe.""" + return self.sutabel_data_df['ln(su)'].sum() + + +def sum_sv_tt_sutabel(self: "SUTABEL"): + """Berekent de som van s_tt waarden in de sutabel dataframe.""" + return self.sutabel_data_df['s_tt'].sum() + + +def sum_sv_ty_sutabel(self: "SUTABEL"): + """Berekent de som van s_ty waarden in de sutabel dataframe.""" + return self.sutabel_data_df['s_ty'].sum() + + +def e_a2_sutabel(self: "SUTABEL"): + """Berekent de helling (a2) van de lineaire regressie voor sutabel analyse.""" + return sum_sv_ty_sutabel(self) / sum_sv_tt_sutabel(self) + + +def e_a1_sutabel(self: "SUTABEL"): + """Berekent het snijpunt (a1) van de lineaire regressie voor sutabel analyse.""" + return (sum_ln_su_sutabel(self) - sum_ln_sv_sutabel(self) * e_a2_sutabel(self)) / count_ln_sv_sutabel(self) + + +def sum_chi_2_sutabel(self: "SUTABEL"): + """Berekent de som van chi-kwadraat waarden in de sutabel dataframe.""" + return self.sutabel_data_df['chi_2'].sum() + + +def var_a2_sutabel(self: "SUTABEL"): + """Berekent de variantie van a2 voor sutabel analyse.""" + return (1 / sum_sv_tt_sutabel(self)) * sum_chi_2_sutabel(self) / (count_ln_sv_sutabel(self) - 2) + + +def var_a1_sutabel(self: "SUTABEL"): + """Berekent de variantie van a1 voor sutabel analyse.""" + return 1 / count_ln_sv_sutabel(self) * (1 + sum_ln_sv_sutabel(self) ** 2 / (count_ln_sv_sutabel(self) * sum_sv_tt_sutabel(self))) * sum_chi_2_sutabel(self) / ( + count_ln_sv_sutabel(self) - 2) + + +def cov_a1_a2_sutabel(self: "SUTABEL"): + """Berekent de covariantie tussen a1 en a2 voor sutabel analyse.""" + return -(sum_ln_sv_sutabel(self) / (count_ln_sv_sutabel(self) * sum_sv_tt_sutabel(self))) * sum_chi_2_sutabel(self) / (count_ln_sv_sutabel(self) - 2) + + +def rho_a1_a2_sutabel(self: "SUTABEL"): + """Berekent de correlatie tussen a1 en a2 voor sutabel analyse.""" + return cov_a1_a2_sutabel(self) / (var_a2_sutabel(self) * var_a1_sutabel(self)) ** 0.5 + + +def sigma_a2_sutabel(self: "SUTABEL"): + """Berekent de standaarddeviatie van a2 voor sutabel analyse.""" + return np.sqrt(var_a2_sutabel(self)) + + +def sigma_a1_sutabel(self: "SUTABEL"): + """Berekent de standaarddeviatie van a1 voor sutabel analyse.""" + return np.sqrt(var_a1_sutabel(self)) + + +def t_n_2_sutabel(self: "SUTABEL"): + """Berekent de t-waarde voor sutabel analyse.""" + significantieniveau = 0.1 + degrees_of_freedom = count_ln_sv_sutabel(self) - 2 + return t.ppf(1 - significantieniveau / 2, degrees_of_freedom) + + +def sum_s_eff_sutabel(self: "SUTABEL"): + """Berekent de som van effectieve spanning waarden in de sutabel dataframe.""" + return self.sutabel_data_df['s\''].sum() + + +def count_s_eff_sutabel(self: "SUTABEL"): + """Telt het aantal effectieve spanning waarden in de sutabel dataframe.""" + return self.sutabel_data_df['s\''].count() + + +def sum_5_pr_ondergrens_sutabel(self: "SUTABEL"): + """Berekent de som van 5% ondergrens waarden in de sutabel dataframe.""" + return self.sutabel_data_df['5_pr_ondergrens'].sum() + + +def sum_stt_ondergrens_sutabel(self: "SUTABEL"): + """Berekent de som van s_tt ondergrens waarden in de sutabel dataframe.""" + return self.sutabel_data_df['s_tt_ondergrens'].sum() + + +def sum_sty_ondergrens_sutabel(self: "SUTABEL"): + """Berekent de som van s_ty ondergrens waarden in de sutabel dataframe.""" + return self.sutabel_data_df['s_ty_ondergrens'].sum() + + +def a2_kar_sutabel(self: "SUTABEL"): + """Berekent de karakteristieke helling (a2) voor sutabel analyse.""" + return sum_sty_ondergrens_sutabel(self) / sum_stt_ondergrens_sutabel(self) + + +def a1_kar_sutabel(self: "SUTABEL"): + """Berekent het karakteristieke snijpunt (a1) voor sutabel analyse.""" + return (sum_5_pr_ondergrens_sutabel(self) - a2_kar_sutabel(self) * sum_s_eff_sutabel(self)) / count_s_eff_sutabel(self) + + +def steyx_sutabel(self: "SUTABEL"): + """Berekent de STEYX (standaardfout van de schatting) voor ln(s'v) en ln(su) data.""" + # Bereken de residuen + n = count_ln_sv_sutabel(self) + sum_squared_residuals = sum_chi_2_sutabel(self) + + # STEYX formule: sqrt(sum((y - y_pred)^2) / (n - 2)) + steyx = np.sqrt(sum_squared_residuals / (n - 2)) + + return steyx + diff --git a/pv_tool/sutabel_analysis/visualization_sutabel.py b/pv_tool/sutabel_analysis/visualization_sutabel.py new file mode 100644 index 0000000..009f66c --- /dev/null +++ b/pv_tool/sutabel_analysis/visualization_sutabel.py @@ -0,0 +1,342 @@ +""" +Visualisatie functies voor Sutabel-m analyse. + +Deze module bevat functies voor het visualiseren van sutabel-m analyseresultaten +met Plotly. +""" + +from typing import TYPE_CHECKING +import plotly.graph_objects as go +import numpy as np + +if TYPE_CHECKING: + from pv_tool.sutabel_analysis.sutabel_analysis import SUTABEL + +from typing import List, Optional +from pv_tool.shansep_analysis.globals import TEXTUAL_NAMES, TEXTUAL_NAMES_DSS, NEW_COLUMN_NAMES + +# ========================== SUTABEL LN-LN PLOT ========================== + +def add_proefresultaten_ln_sv_ln_su_sutabel(self: "SUTABEL"): + """Voegt de proefresultaten toe aan de figuur voor sutabel analyse (ln(s'v) vs ln(su)).""" + boring_monsternummer = self.sutabel_data_df.index + + x_proefresultaten = self.sutabel_data_df['ln(s\'v)'] + y_proefresultaten = self.sutabel_data_df['ln(su)'] + + self.figure.add_trace( + go.Scatter( + x=x_proefresultaten, + y=y_proefresultaten, + mode='markers', + marker=dict( + color='blue' + ), + name=f'Geanalyseerd: {self.investigation_groups[0]} (OC)', + text=boring_monsternummer, + hoverinfo='text' + ) + ) + +def get_extra_data(self: "SUTABEL", investigationgroups_extra: Optional[List]): + if self.analysis_type in ['TXT_su_tabel']: + dataset_df = self.dbase_df[self.dbase_df['ALG__TRIAXIAAL']] + elif self.analysis_type in ['DSS_su_tabel']: + dataset_df = self.dbase_df[self.dbase_df['ALG__DSS']] + else: + raise ValueError(f"analysis type for extra dataset not right: {self.analysis_type}") + + dataset_df = dataset_df[ + dataset_df['PV_NAAM'].isin(investigationgroups_extra)] + if self.analysis_type in ['DSS_su_tabel']: + dataset_df = dataset_df[TEXTUAL_NAMES_DSS.get(self.effective_stress, [])] + else: + dataset_df = dataset_df[TEXTUAL_NAMES.get(self.effective_stress, [])] + dataset_df.columns = NEW_COLUMN_NAMES + return dataset_df + +def add_extra_proefresultaten_ln_sv_ln_su_sutabel(self: "SUTABEL", extra_groepen: Optional[List]): + """Deze functie voegt de proefresultaten toe aan de figuur.""" + df = get_extra_data(self, investigationgroups_extra=extra_groepen) + df = df[df['consolidatietype'] == 'OC'].copy() + + # paar zaken berekenen van deze nieuwe data + df.loc[:, 'ln(s\'v)'] = ( + df['S\'v'].apply + (lambda x: np.log(x) if x is not None and x > 0 else "")) + + df.loc[:, 'ln(su)'] = ( + df['Su'].apply + (lambda x: np.log(x) if x is not None and x > 0 else "")) + + boring_monsternummer = df.index + + n=0 + for naam in df['PV_NAAM'].unique(): + + sub_df = df[df['PV_NAAM'] == naam] + x_extra_proefresultaten = self.sutabel_data_df['ln(s\'v)'] + y_extra_proefresultaten = self.sutabel_data_df['ln(su)'] + + self.figure.add_trace( + go.Scatter( + x=x_extra_proefresultaten, + y=y_extra_proefresultaten, + mode='markers', + name=f'Extra: {extra_groepen[n]}', + text=boring_monsternummer, + hoverinfo='text' + ) + ) + n += 1 + +def add_lineair_fit_ln_sv_ln_su_sutabel(self: "SUTABEL"): + """Voegt de lineaire fit toe aan de figuur voor sutabel analyse.""" + # Gebruik de min en max van ln(s'v) voor de lijn + x1 = self.sutabel_data_df['ln(s\'v)'].min() + x2 = self.sutabel_data_df['ln(s\'v)'].max() + + # Gebruik de berekende regressie parameters + from pv_tool.sutabel_analysis.variables import e_a2_sutabel, e_a1_sutabel + a2 = e_a2_sutabel(self) + a1 = e_a1_sutabel(self) + + # Bereken y-waarden met de regressievergelijking: ln(su) = a1 + a2 * ln(s'v) + y1 = a1 + a2 * x1 + y2 = a1 + a2 * x2 + + x = [x1, x2] + y = [y1, y2] + + self.figure.add_trace( + go.Scatter( + x=x, + y=y, + mode='lines', + name='Lineaire fit proefresultaten', + line=dict(color='green', width=2), + ) + ) + +def add_5pr_bovengrens_ln_sv_ln_su_sutabel(self: "SUTABEL"): + """Voegt de 5% bovengrens toe aan de figuur voor sutabel analyse.""" + x_5pr = self.sutabel_data_df['s\''] + y_5pr = self.sutabel_data_df['5_pr_bovengrens'] + + self.figure.add_trace( + go.Scatter( + x=x_5pr, + y=y_5pr, + mode='lines', + name='5% bovengrens', + line=dict( + color='black', + width=1, + dash='dash' + ) + ) + ) + +def add_5pr_ondergrens_ln_sv_ln_su_sutabel(self: "SUTABEL"): + """Voegt de 5% ondergrens toe aan de figuur voor sutabel analyse.""" + x_5pr = self.sutabel_data_df['s\''] + y_5pr = self.sutabel_data_df['5_pr_ondergrens'] + + self.figure.add_trace( + go.Scatter( + x=x_5pr, + y=y_5pr, + mode='lines', + name='5% ondergrens', + line=dict( + color='black', + width=1, + dash='dash' + ) + ) + ) + +def add_fysische_realiseerbare_ondergrens_ln_sv_ln_su_sutabel(self: "SUTABEL"): + """Voegt de fysische realiseerbare ondergrens toe aan de figuur voor sutabel analyse.""" + from pv_tool.sutabel_analysis.variables import a2_kar_sutabel, a1_kar_sutabel + + # Bereken de karakteristieke lijn: ln(su) = a1_kar + a2_kar * ln(s'v) + x1 = self.sutabel_data_df['ln(s\'v)'].min() + x2 = self.sutabel_data_df['ln(s\'v)'].max() + + # Gebruik handmatige parameters als deze zijn ingesteld, anders berekende waarden + if hasattr(self, 'parameters_handmatig') and self.parameters_handmatig: + # Gebruik handmatige waarden als beschikbaar + a2_kar = self.a2_kar_handmatig if self.a2_kar_handmatig is not None else a2_kar_sutabel(self) + a1_kar = self.a1_kar_handmatig if self.a1_kar_handmatig is not None else a1_kar_sutabel(self) + else: + # Gebruik berekende waarden + a2_kar = a2_kar_sutabel(self) + a1_kar = a1_kar_sutabel(self) + + y1 = a1_kar + a2_kar * x1 + y2 = a1_kar + a2_kar * x2 + + x = [x1, x2] + y = [y1, y2] + + self.figure.add_trace( + go.Scatter( + x=x, + y=y, + mode='lines', + name='Fysische realiseerbare ondergrens', + line=dict(color='black', width=2), + ) + ) + +def set_layout_ln_sv_ln_su_sutabel(self: "SUTABEL"): + """Stelt de layout van de figuur in voor sutabel analyse (ln(s'v) vs ln(su)).""" + title = f'Sutabel-m analyse: ln(s\'v) vs ln(su) - {self.analysis_type} proef' + + xas_title = r"$\ln(\sigma'_{v}) [-]$" + yas_title = r'$\ln(s_{u}) [-]$' + + legend_title = 'Legenda' + self.figure.update_layout( + width=1280, + height=720, + title=title if self.show_title else None, + xaxis_title=xas_title, + yaxis_title=yas_title, + legend_title=legend_title, + margin=dict(t=100, r=50, b=100, l=50) + ) + +# ========================== SUTABEL SV-SU PLOT ========================== + +def add_proefresultaten_sv_su_sutabel(self: "SUTABEL"): + """Voegt de proefresultaten toe aan de figuur voor sutabel analyse (s'v vs su).""" + boring_monsternummer = self.sutabel_data_df.index + + x_proefresultaten = self.sutabel_data_df['S\'v'] + y_proefresultaten = self.sutabel_data_df['Su'] + + self.figure.add_trace( + go.Scatter( + x=x_proefresultaten, + y=y_proefresultaten, + mode='markers', + marker=dict( + color='blue' + ), + name=f'Geanalyseerd: {self.investigation_groups[0]} (OC)', + text=boring_monsternummer, + hoverinfo='text' + ) + ) + +def add_extra_proefresultaten_sv_su_sutabel(self: "SUTABEL", extra_groepen: Optional[List]): + """Deze functie voegt de proefresultaten toe aan de figuur.""" + df = get_extra_data(self, investigationgroups_extra=extra_groepen) + df = df[df['consolidatietype'] == 'OC'].copy() + boring_monsternummer = df.index + + n=0 + for naam in df['PV_NAAM'].unique(): + + sub_df = df[df['PV_NAAM'] == naam] + x_extra_proefresultaten = self.sutabel_data_df['S\'v'] + y_extra_proefresultaten = self.sutabel_data_df['Su'] + + self.figure.add_trace( + go.Scatter( + x=x_extra_proefresultaten, + y=y_extra_proefresultaten, + mode='markers', + name=f'Extra: {extra_groepen[n]}', + text=boring_monsternummer, + hoverinfo='text' + ) + ) + n += 1 + +def add_sutabel_kar_line(self: "SUTABEL"): + """ + Voegt de karakteristieke sutabel lijn toe aan de s'v vs su plot. + + Deze lijn wordt berekend met: su_kar = svgm_kar * (s'v)^(1-m_kar) + """ + if self.sutabel_grafiek is None: + return + + x = self.sutabel_grafiek["s'v [kPa]"].tolist() + y = self.sutabel_grafiek["su_kar [kPa]"].tolist() + + self.figure.add_trace( + go.Scatter( + x=x, + y=y, + mode='lines', + name='Sutabel karakteristiek', + line=dict(color='black', width=2) + ) + ) + +def add_sutabel_gem_line(self: "SUTABEL"): + """ + Voegt de gemiddelde sutabel lijn toe aan de s'v vs su plot. + + Deze lijn wordt berekend met: su_gem = svgm_gem * (s'v)^(1-m_gem) + """ + if self.sutabel_grafiek is None: + return + + x = self.sutabel_grafiek["s'v [kPa]"].tolist() + y = self.sutabel_grafiek["su_gem [kPa]"].tolist() + + self.figure.add_trace( + go.Scatter( + x=x, + y=y, + mode='lines', + name='Sutabel gemiddeld', + line=dict(color='purple', width=2, dash='dot') + ) + ) + +def add_su_kar_fit_constante_vc(self: "SUTABEL"): + """ + Voegt de karakteristieke su fit met constante s'vc toe aan de s'v vs su plot. + + Deze lijn wordt berekend met lognormale verdeling: + su_kar_fit = LOGNORM.INV(0.05; ln(su_gem); STDEV_logn_vc) + """ + if self.su_fit_constante_vc is None: + return + + x = self.su_fit_constante_vc["s'v [kPa]"].tolist() + y = self.su_fit_constante_vc["su_kar fit met constante vc [kPa]"].tolist() + + self.figure.add_trace( + go.Scatter( + x=x, + y=y, + mode='lines', + name='Su kar fit met constante vc', + line=dict(color='red', width=2, dash='dashdot') + ) + ) + +def set_layout_sv_su_sutabel(self: "SUTABEL"): + """Stelt de layout van de figuur in voor sutabel analyse (s'v vs su).""" + title = f'Sutabel-m analyse: s\'v vs su - {self.analysis_type} proef' + + xas_title = r"$\sigma'_{v} [kPa]$" + yas_title = r'$s_{u} [kPa]$' + + legend_title = 'Legenda' + self.figure.update_layout( + width=1280, + height=720, + title=title if self.show_title else None, + xaxis_title=xas_title, + yaxis_title=yas_title, + legend_title=legend_title, + margin=dict(t=100, r=50, b=100, l=50) + ) \ No newline at end of file