Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 69 additions & 29 deletions HEC.FDA.Model/compute/ImpactAreaScenarioSimulation.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace HEC.FDA.ModelTest.unittests;

[Trait("RunsOn","Remote")]
/// <summary>
/// Tests that the consolidated EAD methods correctly return life loss (AALL) reduced results
/// alongside damage reduced results, verifying that the separate AALL computation path is no longer needed.
Expand Down
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these tests look like they repeat the same object initialization for setup (discharge-frequency, etc.). I don't know if that matters between tests, but would it be useful to separate that out into a helper instead of repeating code in multiple tests?

Large diffs are not rendered by default.

48 changes: 45 additions & 3 deletions HEC.FDA.TestingUtility/ComputeRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,10 @@ public async Task<int> RunAsync()
switch (compute.Type.ToLowerInvariant())
{
case "stagedamage":
List<UncertainPairedData> sdCurves = StageDamageRunner.RunStageDamage(compute.ElementName);
SaveStageDamageResults(compute.ElementName, sdCurves);
_csvReportFactory.AddStageDamageSummary(study.StudyId, compute.ElementName, sdCurves);
StageDamageResult sdResult = StageDamageRunner.RunStageDamage(compute.ElementName);
SaveStageDamageResults(compute.ElementName, sdResult.StageDamageFunctions);
WriteStructureDetails(study.StudyId, compute.ElementName, sdResult);
_csvReportFactory.AddStageDamageSummary(study.StudyId, compute.ElementName, sdResult.StageDamageFunctions);
break;

case "scenario":
Expand Down Expand Up @@ -323,4 +324,45 @@ private static void SaveStageDamageResults(string elementName, List<UncertainPai
PersistenceFactory.GetElementManager<AggregatedStageDamageElement>().SaveExisting(element);
Console.WriteLine($" Saved {curves.Count} curves to temp database.");
}

private void WriteStructureDetails(string studyId, string elementName, StageDamageResult sdResult)
{
if (sdResult.ScenarioStageDamage == null)
{
Console.WriteLine($" Skipping structure details for manual stage damage '{elementName}'.");
return;
}

string detailsDir = Path.Combine(_outputDir, studyId, "StructureDetails");
Directory.CreateDirectory(detailsDir);

Dictionary<int, string> iaNames = [];
List<ImpactAreaRowItem> iaRows = sdResult.ImpactAreaElement.ImpactAreaRows;
for (int i = 0; i < iaRows.Count; i++)
{
iaNames[i] = iaRows[i].Name;
}

string detailsPath = Path.Combine(detailsDir, $"{elementName}_StructureStageDamageDetails.csv");
List<string> structureDetails = sdResult.ScenarioStageDamage.ProduceStructureDetails(iaNames);
using (StreamWriter writer = new(File.Create(detailsPath)))
{
foreach (string line in structureDetails)
{
writer.WriteLine(line);
}
}
Console.WriteLine($" Wrote structure details to {detailsPath}");

string damagedElesPath = Path.Combine(detailsDir, $"{elementName}_DamagedElementCountsByStage.csv");
List<string> damagedElementCounts = UncertainPairedData.ConvertDamagedElementCountToText(sdResult.DamagedElementCounts, iaNames);
using (StreamWriter writer = new(File.Create(damagedElesPath)))
{
foreach (string line in damagedElementCounts)
{
writer.WriteLine(line);
}
}
Console.WriteLine($" Wrote damaged element counts to {damagedElesPath}");
}
}
84 changes: 68 additions & 16 deletions HEC.FDA.TestingUtility/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@ public static async Task<int> Main(string[] args)
// ============ COMPUTE COMMAND ============
Command computeCommand = new("compute", "Run computations on FDA studies and generate CSV result reports");

Option<FileInfo> computeConfigOption = new(
Option<FileInfo?> computeConfigOption = new(
name: "--config",
description: "Path to JSON configuration file")
{ IsRequired = true };
description: "Path to JSON configuration file");
computeConfigOption.AddAlias("-c");

Option<DirectoryInfo> computeOutputOption = new(
Option<FileInfo?> studyPathOption = new(
name: "--study-path",
description: "Path to a .sqlite study file (runs all computations)");
studyPathOption.AddAlias("-sp");

Option<DirectoryInfo?> computeOutputOption = new(
name: "--output",
description: "Output directory for generated files",
getDefaultValue: () => new DirectoryInfo(Environment.CurrentDirectory));
description: "Output directory for generated files");
computeOutputOption.AddAlias("-o");

Option<string[]> computeStudyOption = new(
Expand All @@ -36,34 +39,83 @@ public static async Task<int> Main(string[] args)
computeStudyOption.AddAlias("-s");

computeCommand.AddOption(computeConfigOption);
computeCommand.AddOption(studyPathOption);
computeCommand.AddOption(computeOutputOption);
computeCommand.AddOption(computeStudyOption);

computeCommand.SetHandler(async (configFile, outputDir, studyFilter) =>
computeCommand.SetHandler(async (configFile, studyPath, outputDir, studyFilter) =>
{
try
{
Console.WriteLine("FDA Testing Utility - Compute");
Console.WriteLine("=============================");
Console.WriteLine();

if (!configFile.Exists)
if (configFile != null && studyPath != null)
{
Console.WriteLine("Error: Cannot specify both --config and --study-path. Use one or the other.");
return;
}

if (configFile == null && studyPath == null)
{
Console.WriteLine($"Error: Configuration file not found: {configFile.FullName}");
Console.WriteLine("Error: Must specify either --config or --study-path.");
return;
}

Console.WriteLine($"Loading configuration: {configFile.FullName}");
TestConfiguration config = TestConfiguration.LoadFromFile(configFile.FullName);
TestConfiguration config;
string outputPath;

if (!outputDir.Exists)
if (studyPath != null)
{
if (!studyPath.Exists)
{
Console.WriteLine($"Error: Study file not found: {studyPath.FullName}");
return;
}

string studyName = Path.GetFileNameWithoutExtension(studyPath.Name);
string studyDir = studyPath.DirectoryName!;

Console.WriteLine($"Direct study mode: {studyPath.FullName}");
config = new TestConfiguration
{
TestSuiteId = $"direct-{studyName}",
Studies = new List<StudyConfiguration>
{
new()
{
StudyId = studyName,
StudyName = studyName,
NetworkSourcePath = studyDir,
RunAllStageDamage = true,
RunAllScenarios = true,
RunAllAlternatives = true,
RunAllAlternativeComparisons = true,
}
}
};

outputPath = outputDir?.FullName ?? studyDir;
}
else
{
outputDir.Create();
if (!configFile!.Exists)
{
Console.WriteLine($"Error: Configuration file not found: {configFile.FullName}");
return;
}

Console.WriteLine($"Loading configuration: {configFile.FullName}");
config = TestConfiguration.LoadFromFile(configFile.FullName);
outputPath = outputDir?.FullName ?? Environment.CurrentDirectory;
}

Directory.CreateDirectory(outputPath);

ComputeRunner runner = new(
config,
outputDir.FullName,
outputPath,
studyFilter?.Length > 0 ? studyFilter : null);

await runner.RunAsync();
Expand All @@ -73,12 +125,12 @@ public static async Task<int> Main(string[] args)
Console.WriteLine($"Fatal error: {ex.Message}");
Console.WriteLine(ex.StackTrace);
}
}, computeConfigOption, computeOutputOption, computeStudyOption);
}, computeConfigOption, studyPathOption, computeOutputOption, computeStudyOption);

// Add subcommands to root
rootCommand.AddCommand(computeCommand);

// Run
return await rootCommand.InvokeAsync(args);
}
}
}
25 changes: 16 additions & 9 deletions HEC.FDA.TestingUtility/Services/StageDamageRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@
using HEC.FDA.ViewModel.ImpactArea;
using HEC.FDA.ViewModel.Inventory;
using HEC.FDA.ViewModel.Utilities;
using Utility.Progress;

namespace HEC.FDA.TestingUtility.Services;

public record StageDamageResult(
List<UncertainPairedData> StageDamageFunctions,
List<UncertainPairedData> DamagedElementCounts,
ScenarioStageDamage? ScenarioStageDamage,
ImpactAreaElement ImpactAreaElement);

public static class StageDamageRunner
{
public static List<UncertainPairedData> RunStageDamage(string elementName)
public static StageDamageResult RunStageDamage(string elementName)
{
if (string.IsNullOrWhiteSpace(elementName))
{
Expand All @@ -23,19 +30,19 @@ public static List<UncertainPairedData> RunStageDamage(string elementName)

AggregatedStageDamageElement element = ScenarioRunner.FindElement<AggregatedStageDamageElement>(elementName);

if (element.IsManual)
{
Console.WriteLine($" Stage damage '{elementName}' is manual - returning existing curves.");
return ConvertCurvesToUPD(element.Curves);
}

var impactAreaElements = BaseViewModel.StudyCache.GetChildElementsOfType<ImpactAreaElement>();
if (impactAreaElements.Count == 0)
{
throw new InvalidOperationException("No impact area element found in study.");
}
ImpactAreaElement impactAreaElement = impactAreaElements[0];

if (element.IsManual)
{
Console.WriteLine($" Stage damage '{elementName}' is manual - returning existing curves.");
return new StageDamageResult(ConvertCurvesToUPD(element.Curves), [], null, impactAreaElement);
}

HydraulicElement hydraulicElement = ScenarioRunner.FindElementById<HydraulicElement>(element.SelectedWSE);
InventoryElement inventoryElement = ScenarioRunner.FindElementById<InventoryElement>(element.SelectedStructures);

Expand Down Expand Up @@ -72,11 +79,11 @@ public static List<UncertainPairedData> RunStageDamage(string elementName)

Console.WriteLine($" Computing stage damage with {totalStructureCount} structures...");

(List<UncertainPairedData> stageDamageFunctions, _) = scenarioStageDamage.Compute();
(List<UncertainPairedData> stageDamageFunctions, List<UncertainPairedData> damagedElementCounts) = scenarioStageDamage.Compute(false,ProgressReporter.ConsoleWrite());

Console.WriteLine($" Stage damage computation complete. Generated {stageDamageFunctions.Count} curves.");

return stageDamageFunctions;
return new StageDamageResult(stageDamageFunctions, damagedElementCounts, scenarioStageDamage, impactAreaElement);
}

private static List<UncertainPairedData> ConvertCurvesToUPD(List<StageDamageCurve> curves)
Expand Down
41 changes: 20 additions & 21 deletions HEC.FDA.TestingUtility/Services/StudyLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ private static void CopyDirectory(string sourceDir, string destDir)

private static string FindDatabaseFile(string studyPath)
{
// Look for .sqlite or .db files
// Look for .sqlite or .db files recursively
string[] dbExtensions = { "*.sqlite", "*.db" };

foreach (string pattern in dbExtensions)
{
string[] files = Directory.GetFiles(studyPath, pattern);
string[] files = Directory.GetFiles(studyPath, pattern, SearchOption.AllDirectories);
if (files.Length > 0)
{
return files[0];
Expand Down Expand Up @@ -130,30 +130,29 @@ private static void LoadElementType<T>(string displayName) where T : ChildElemen

public void Cleanup()
{
if (_localStudyPath != null && Directory.Exists(_localStudyPath))
try
{
try
// Close the connection first
if (!Connection.Instance.IsConnectionNull && Connection.Instance.IsOpen)
{
// Close the connection first
if (!Connection.Instance.IsConnectionNull && Connection.Instance.IsOpen)
{
Connection.Instance.Close();
}
Connection.Instance.Close();
}

// Clear SQLite connection pool to release file handles
SQLiteConnection.ClearAllPools();
// Clear SQLite connection pool to release file handles
SQLiteConnection.ClearAllPools();

// Force garbage collection to release any remaining handles
GC.Collect();
GC.WaitForPendingFinalizers();
// Force garbage collection to release any remaining handles
GC.Collect();
GC.WaitForPendingFinalizers();
}
catch (Exception ex)
{
Console.WriteLine($" Warning: Failed to close connections: {ex.Message}");
}

Directory.Delete(_localStudyPath, recursive: true);
Console.WriteLine($" Cleaned up temp study folder: {_localStudyPath}");
}
catch (Exception ex)
{
Console.WriteLine($" Warning: Failed to cleanup temp folder: {ex.Message}");
}
if (_localStudyPath != null)
{
Console.WriteLine($" Temp study folder retained at: {_localStudyPath}");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,16 @@
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedLeveeFeatureElement}" />

<!--<Label Grid.Column="2"
<Label Grid.Column="2"
Content="{x:Static utilVM:StringConstants.EXT_INT_SHORT_LABEL}"
Visibility="{Binding HasNonFailureStageDamage,Converter={StaticResource BoolToInverseVisibilityConverter}}"
HorizontalAlignment="Right" />-->
<!--<ComboBox Grid.Column="3"
HorizontalAlignment="Right" />
<ComboBox Grid.Column="3"
HorizontalAlignment="Stretch"
ItemsSource="{Binding ExteriorInteriorElements}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedExteriorInteriorElement}"
Visibility="{Binding HasNonFailureStageDamage,Converter={StaticResource BoolToInverseVisibilityConverter}}" />-->
Visibility="{Binding HasNonFailureStageDamage,Converter={StaticResource BoolToInverseVisibilityConverter}}" />


</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ private void PreviewCompute()
else if(_SelectedStageDamage() == null && _HasFailureStageDamage == true){
}

ChildElementComboItem selectedStageDamage = _SelectedStageDamage();
ChildElementComboItem selectedStageDamage = _SelectedStageDamage();
FrequencyElement freqElem = SelectedFrequencyElement.ChildElement as FrequencyElement;
InflowOutflowElement inOutElem = SelectedInflowOutflowElement.ChildElement as InflowOutflowElement;
StageDischargeElement ratElem = SelectedRatingCurveElement.ChildElement as StageDischargeElement;
Expand Down
5 changes: 3 additions & 2 deletions ScratchSpace/Beam/ValidationAndVerification.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Amazon.Auth.AccessControlPolicy;
using System;
using System.IO;
using System.Threading.Tasks;
Expand All @@ -7,14 +8,14 @@ namespace ScratchSpace.Beam;
public static class ValidationAndVerification
{
private static readonly string ConfigPath = Path.GetFullPath(
Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "Beam", "west-sac-config.json"));
Path.Combine(@"C:\Programs\Source\HEC-FDA2\HEC.FDA.TestingUtility\all_case_studies.json"));

private static readonly string OutputPath = Path.GetFullPath(
Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "Beam", "output"));

public static async Task<int> RunValidationAndVerificationReport()
{
string[] args = ["compute", "-c", ConfigPath, "-o", OutputPath];
string[] args = ["compute", "-c", ConfigPath];
return await HEC.FDA.TestingUtility.Program.Main(args);
}
}
Loading