// Copyright (C) Stichting Deltares 2018. All rights reserved.
//
// This file is part of the Dam Engine.
//
// The Dam Engine is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
//
// All names, logos, and references to "Deltares" are registered trademarks of
// Stichting Deltares and remain full property of Stichting Deltares at all times.
// All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Deltares.DamEngine.Calculators.KernelWrappers.Common;
using Deltares.DamEngine.Calculators.KernelWrappers.Interfaces;
using Deltares.DamEngine.Calculators.Properties;
using Deltares.DamEngine.Data.Design;
using Deltares.DamEngine.Data.General.Results;
using Deltares.DamEngine.Data.Geometry;
using Deltares.DamEngine.Data.Geotechnics;
using Deltares.DamEngine.Data.Standard.Logging;
namespace Deltares.DamEngine.Calculators.DikesDesign
{
///
/// The Dam Engine design calculator
///
public class DesignCalculatorShoulderPerPoint
{
private const double minimumShoulderElevation = 0.5;
private const double minimumShoulderWidth = 2;
private const double toleranceShoulderChanges = 0.001;
///
/// Performs the design calculation shoulder iterative per point.
/// This is a design strategy used for Piping
///
/// The kernel wrapper.
/// The kernel data input.
/// The kernel data output.
/// The dam kernel input.
/// The design scenario.
/// The calculation messages.
/// The design calculations.
///
public static void PerformDesignCalculationShoulderPerPoint
(IKernelWrapper kernelWrapper, IKernelDataInput kernelDataInput,
IKernelDataOutput kernelDataOutput, DamKernelInput damKernelInput,
DesignScenario designScenario,
List calculationMessages, List designCalculations)
{
var designResults = new List();
var location = damKernelInput.Location;
var surfaceLine = location.SurfaceLine;
var orgSurfaceLine = surfaceLine;
try
{
List locationCalculationMessages;
double orgShoulderLength = surfaceLine.DetermineShoulderWidth();
double orgShoulderHeight = surfaceLine.DetermineShoulderHeight();
GeometryPoint startSurfacePoint = surfaceLine.GetDikeToeInward();
IEnumerable relevantSurfacePointsList = from GeometryPoint point in surfaceLine.Geometry.Points
where point.X >= startSurfacePoint.X
orderby point.X ascending
select point;
relevantSurfacePointsList = GetDiscretizedSurfaceLine(relevantSurfacePointsList);
double desiredShoulderLength = orgShoulderLength;
double oldDesiredShoulderLength = orgShoulderLength;
double desiredShoulderHeight = orgShoulderHeight;
double oldDesiredShoulderHeight = orgShoulderHeight;
foreach (var point in relevantSurfacePointsList)
{
// Calculate the piping design at the given point. This returns the required adaption (berm length and height) if any.
ShoulderDesign shoulderDesign = kernelWrapper.CalculateDesignAtPoint(damKernelInput, kernelDataInput, kernelDataOutput, point, out locationCalculationMessages);
if (shoulderDesign != null)
{
// Piping is an issue so adapt the surfaceline for it
desiredShoulderLength = shoulderDesign.ShoulderLengthFromToe;
desiredShoulderLength = Math.Max(desiredShoulderLength, oldDesiredShoulderLength);
oldDesiredShoulderLength = desiredShoulderLength;
// shoulder height is height above surfacelevel!!
desiredShoulderHeight = shoulderDesign.ShoulderHeightFromToe;
desiredShoulderHeight = Math.Max(desiredShoulderHeight, oldDesiredShoulderHeight);
oldDesiredShoulderHeight = desiredShoulderHeight;
}
}
if (desiredShoulderLength > 0)
{
desiredShoulderLength = Math.Max(desiredShoulderLength, minimumShoulderWidth);
}
if (desiredShoulderLength > 0)
{
desiredShoulderHeight = Math.Max(desiredShoulderHeight, minimumShoulderElevation);
}
bool isNewShoulderSameAsOriginal = ((Math.Abs(desiredShoulderLength - orgShoulderLength) < toleranceShoulderChanges) &&
(Math.Abs(desiredShoulderHeight - orgShoulderHeight) < toleranceShoulderChanges));
SurfaceLine2 newSurfaceLine;
if (isNewShoulderSameAsOriginal)
{
newSurfaceLine = surfaceLine;
}
else
{
// Adapt the surfaceline for the finally required shoulder dimensions.
double maxShoulderLevel = CalculateMaximumShoulderLevel(surfaceLine, 1.0); // no limit to height of shoulder
var surfaceLineShoulderAdapter = new SurfaceLineShoulderAdapter(surfaceLine, location);
surfaceLineShoulderAdapter.MaxShoulderLevel = maxShoulderLevel;
newSurfaceLine = surfaceLineShoulderAdapter.ConstructNewSurfaceLine(desiredShoulderLength, desiredShoulderHeight, true);
}
damKernelInput.Location.SurfaceLine = newSurfaceLine;
kernelWrapper.Prepare(damKernelInput, 0, out kernelDataInput, out kernelDataOutput);
kernelWrapper.Execute(kernelDataInput, kernelDataOutput, out locationCalculationMessages);
// Process output
calculationMessages.AddRange(locationCalculationMessages);
StringBuilder sb = new StringBuilder();
foreach (var message in locationCalculationMessages)
{
sb.Append(message.Message + Environment.NewLine);
}
string resultMessage = sb.ToString();
kernelWrapper.PostProcess(damKernelInput, kernelDataOutput, designScenario, resultMessage, out designResults);
string evaluationMessage;
DesignAdvise designAdvise;
bool designSuccessful = kernelWrapper.EvaluateDesign(damKernelInput, kernelDataInput, kernelDataOutput, out designAdvise, out evaluationMessage);
if (!designSuccessful)
{
throw new DesignCalculatorException(Resources.DesignUnsuccessful + " " + evaluationMessage);
}
}
catch (Exception exception)
{
string resultMessage = exception.Message;
kernelWrapper.PostProcess(damKernelInput, kernelDataOutput, designScenario, resultMessage, out designResults);
ChangeSafetyFactor(designResults, -1);
throw new DesignCalculatorException(Resources.DesignUnsuccessful + " " + resultMessage);
}
finally
{
damKernelInput.Location.SurfaceLine = orgSurfaceLine;
foreach (var designResult in designResults)
{
designCalculations.Add(designResult);
}
}
}
private static void ChangeSafetyFactor(List designResults, double safetyFactor)
{
designResults[0].SafetyFactor = 1;
foreach (var designResult in designResults)
{
var factor = designResult.SafetyFactor;
factor = safetyFactor;
designResult.SafetyFactor = factor;
}
}
///
/// Calculates the maximum level for the shoulder.
///
/// The surface line.
/// The fraction of dike height to determine maximimum shoulder height.
///
private static double CalculateMaximumShoulderLevel(SurfaceLine2 surfaceLine, double maxFractionOfDikeHeightForShoulderHeight)
{
var top = surfaceLine.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtPolder).Z;
var bottom = surfaceLine.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtPolder).Z;
if (top - bottom <= 0)
{
throw new DesignCalculatorException(Resources.SurfaceLineShoulderAdapterMaxShoulderHeightError);
}
double maxHeight = Math.Abs((top - bottom) * maxFractionOfDikeHeightForShoulderHeight);
return bottom + maxHeight;
}
///
/// Ensures that the points on the surface line are never more than cDiff (0.5) apart.
///
///
///
private static IEnumerable GetDiscretizedSurfaceLine(IEnumerable originalLine)
{
const double cDiff = 0.5;
var newLine = new List();
double currentX = originalLine.First().X;
foreach (var point in originalLine)
{
while (point.X > currentX + cDiff)
{
var newPoint = new GeometryPoint(point)
{
X = currentX + cDiff
};
if (newPoint.X > newLine.Last().X)
{
newPoint.Z = newLine.Last().Z + ((newPoint.X - newLine.Last().X) / (point.X - newLine.Last().X)) *
(point.Z - newLine.Last().Z);
newLine.Add(newPoint);
}
currentX = newPoint.X;
}
newLine.Add(point);
}
return newLine;
}
}
}