diff --git a/src/Vts.Test/MonteCarlo/DataStructuresValidation/SimulationInputValidationTests.cs b/src/Vts.Test/MonteCarlo/DataStructuresValidation/SimulationInputValidationTests.cs index 46a25526..2c408611 100644 --- a/src/Vts.Test/MonteCarlo/DataStructuresValidation/SimulationInputValidationTests.cs +++ b/src/Vts.Test/MonteCarlo/DataStructuresValidation/SimulationInputValidationTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using NUnit.Framework; using Vts.Common; using Vts.MonteCarlo; @@ -12,133 +13,305 @@ namespace Vts.Test.MonteCarlo.DataStructuresValidation [TestFixture] public class SimulationInputValidationTests { + /// + /// Test to verify input with source initial tissue region index outside number + /// of tissue regions is invalid + /// [Test] - public void validate_null_detector_input_is_invalid_when_no_database_specified() + public void Validate_source_with_initial_tissue_region_index_outside_tissue_range_invalid() + { + // generate input without source initial tissue region in error + var input = new SimulationInput() // default constructor has empty list of databases + { + SourceInput = new CustomPointSourceInput { InitialTissueRegionIndex = 100 } + }; + var result = SimulationInputValidation.ValidateInput(input); + Assert.IsFalse(result.IsValid); + } + + /// + /// Test to verify input with no detectors nor database specified is invalid + /// + [Test] + public void Validate_null_detector_input_is_invalid_when_no_database_specified() { // generate input without any detector inputs and no database specified var input = new SimulationInput() // default constructor has empty list of databases { - DetectorInputs = new List {} + DetectorInputs = new List() }; var result = SimulationInputValidation.ValidateInput(input); Assert.IsFalse(result.IsValid); } + + /// + /// Test to verify input with database specified and no detectors specified is valid + /// [Test] - public void validate_null_detector_input_is_valid_when_database_specified() + public void Validate_null_detector_input_is_valid_when_database_specified() { // generate input without any detector inputs but with database specified - var input = new SimulationInput() + var input = new SimulationInput { - DetectorInputs = new List {} + DetectorInputs = new List(), + Options = + { + Databases = new List {DatabaseType.DiffuseReflectance} + } }; - input.Options.Databases = new List {DatabaseType.DiffuseReflectance}; var result = SimulationInputValidation.ValidateInput(input); Assert.IsTrue(result.IsValid); } + + /// + /// Test to verify input with detector, Radiance(x,y,z,theta,phi) and CAW is invalid + /// because not implemented + /// [Test] - public void validate_detector_input_not_implemented_is_invalid() + public void Validate_detector_input_not_implemented_is_invalid() { // generate input with detector input not implemented yet - var input = new SimulationInput() + var input = new SimulationInput { - Options = new SimulationOptions() { AbsorptionWeightingType = AbsorptionWeightingType.Continuous}, + Options = new SimulationOptions { AbsorptionWeightingType = AbsorptionWeightingType.Continuous}, DetectorInputs = new List { new RadianceOfXAndYAndZAndThetaAndPhiDetectorInput()} }; var result = SimulationInputValidation.ValidateInput(input); Assert.IsFalse(result.IsValid); } + + /// + /// Test to verify that input with two detectors with the same Name is invalid + /// [Test] - public void validate_duplicate_detector_name_is_invalid() + public void Validate_duplicate_detector_name_is_invalid() { // generate input with detector input with duplicate names - var input = new SimulationInput() + var input = new SimulationInput { DetectorInputs = new List { - new ROfRhoDetectorInput() {Name = "ROfRho1"}, - new ROfRhoDetectorInput() {Name = "ROfRho1"} + new ROfRhoDetectorInput {Name = "ROfRho1"}, + new ROfRhoDetectorInput {Name = "ROfRho1"} } }; var result = SimulationInputValidation.ValidateInput(input); Assert.IsFalse(result.IsValid); } + + /// + /// Test to verify that input with Analog specified does not also + /// specify Russian Roulette + /// + [Test] + public void Validate_Analog_with_Russian_Roulette_is_invalid() + { + // generate input with Analog + var input = new SimulationInput(); + input.Options.AbsorptionWeightingType = AbsorptionWeightingType.Analog; + input.Options.RussianRouletteWeightThreshold = 0.1; + var result = SimulationInputValidation.ValidateInput(input); + Assert.IsFalse(result.IsValid); + } + + /// + /// Test to verify input with cylindrical detector and off axis ellipsoid in tissue outputs warning + /// but continues as valid input + /// [Test] - public void validate_ellipsoid_tissue_with_off_zaxis_center_and_cylindrical_detectors_are_not_defined_together() + public void Validate_ellipsoid_tissue_with_off_zaxis_center_and_cylindrical_detectors_issues_warning() { // generate input embedded ellipsoid tissue and cylindrical detector - var input = new SimulationInput() + var input = new SimulationInput { - TissueInput = new SingleEllipsoidTissueInput() + TissueInput = new SingleEllipsoidTissueInput { - EllipsoidRegion = new EllipsoidTissueRegion() {Center = new Position(1, 1, 0)} + EllipsoidRegion = new EllipsoidTissueRegion {Center = new Position(1, 1, 5)} }, DetectorInputs = new List {new ROfRhoDetectorInput()} }; + // set to catch Console output + var output = new StringWriter(); + Console.SetOut(output); var result = SimulationInputValidation.ValidateInput(input); - Assert.IsFalse(result.IsValid); + Assert.IsTrue(result.IsValid); // only warning + Assert.That(output.ToString(), Is.EqualTo("Warning: off center ellipsoid in tissue with cylindrical detector defined: user discretion advised\r\n")); } + + /// + /// Test to verify input cylindrical detector and ellipsoid in tissue issues warning + /// but continues as valid input + /// [Test] - public void validate_ellipsoid_tissue_without_cylindrical_symmetry_and_cylindrical_detectors_are_not_defined_together() + public void Validate_ellipsoid_tissue_without_cylindrical_symmetry_and_cylindrical_detectors_issues_warning() { // generate input embedded ellipsoid tissue and cylindrical detector - var input = new SimulationInput() + var input = new SimulationInput { - TissueInput = new SingleEllipsoidTissueInput() + TissueInput = new SingleEllipsoidTissueInput { - EllipsoidRegion = new EllipsoidTissueRegion() { Dx = 1.0, Dy = 2.0 } + EllipsoidRegion = new EllipsoidTissueRegion { Dx = 1.0, Dy = 2.0 } }, DetectorInputs = new List { new ROfRhoDetectorInput() } }; + // set to catch Console output + var output = new StringWriter(); + Console.SetOut(output); var result = SimulationInputValidation.ValidateInput(input); - Assert.IsFalse(result.IsValid); + Assert.IsTrue(result.IsValid); // only warning + Assert.That(output.ToString(), Is.EqualTo("Warning: ellipsoid with Dx != Dy in tissue with cylindrical detector defined: user discretion advised\r\n")); + } + + /// + /// Test to verify input cylindrical detector and voxel in tissue issues warning + /// but continues as valid input + /// + [Test] + public void Validate_voxel_tissue_and_cylindrical_detectors_issues_warning() + { + // generate input embedded ellipsoid tissue and cylindrical detector + var input = new SimulationInput + { + TissueInput = new SingleVoxelTissueInput + { + VoxelRegion = new VoxelTissueRegion + { + X = new DoubleRange(-1.0, 1.0, 2), + Y = new DoubleRange(-1.0, 1.0,2), + Z = new DoubleRange(0.01, 1, 2) + } + }, + DetectorInputs = new List { new ROfRhoDetectorInput() } + }; + // set to catch Console output + var output = new StringWriter(); + Console.SetOut(output); + var result = SimulationInputValidation.ValidateInput(input); + Assert.IsTrue(result.IsValid); // only warning + Assert.That(output.ToString(), Is.EqualTo("Warning: voxel in tissue with cylindrical detector defined: user discretion advised\r\n")); } + + /// + /// Test to verify input with bounded tissue without ATotalBoundingVolume detector + /// is invalid + /// [Test] - public void validate_angled_source_and_cylindrical_detectors_are_not_defined_together() + public void Validate_bounded_tissue_without_ATotalBoundingVolume_detector_invalid() + { + // generate input with bounding cylinder tissue without needed detector + var input = new SimulationInput + { + TissueInput = new BoundingCylinderTissueInput(), + DetectorInputs = new List { new ROfRhoDetectorInput() } + }; + var result = SimulationInputValidation.ValidateInput(input); + Assert.IsFalse(result.IsValid); + } + + /// + /// Test to verify input with angled source and cylindrical detectors outputs warning + /// but continues as valid input + /// + [Test] + public void Validate_angled_source_and_cylindrical_detectors_are_not_defined_together() { // generate input with angled source and cylindrical detector - var input = new SimulationInput() + var input = new SimulationInput { SourceInput = new DirectionalPointSourceInput( new Position(0,0,0), new Direction(1.0/Math.Sqrt(2), 0, 1.0/Math.Sqrt(2)),1), DetectorInputs = new List { new ROfRhoDetectorInput() } }; + // set to catch Console output + var output = new StringWriter(); + Console.SetOut(output); + var result = SimulationInputValidation.ValidateInput(input); + Assert.IsTrue(result.IsValid); // only warning + Assert.That(output.ToString(), Is.EqualTo("Warning: Angled source and cylindrical coordinate detector defined: user discretion advised\r\n")); + } + + /// + /// Test to verify that input with transmittance detector with final tissue region=0 + /// is invalid + /// + [Test] + public void Validate_transmittance_detector_with_final_tissue_region_equal_0_is_invalid() + { + // generate input with transmittance + var input = new SimulationInput + { + DetectorInputs = new List { + new TOfRhoDetectorInput + { + FinalTissueRegionIndex = 0 + } + } + }; var result = SimulationInputValidation.ValidateInput(input); Assert.IsFalse(result.IsValid); } + + /// + /// Test to verify input with ellipsoid in tissue and R(fx) detector outputs warning + /// but continues as valid input + /// [Test] - public void validate_ellipsoid_tissue_and_ROfFx_detectors_are_not_defined_together() + public void Validate_ellipsoid_tissue_and_ROfFx_detectors_defined_together_issues_warning() { // generate input embedded ellipsoid tissue and cylindrical detector - var input = new SimulationInput() + var input = new SimulationInput { - TissueInput = new SingleEllipsoidTissueInput() + TissueInput = new SingleEllipsoidTissueInput { - EllipsoidRegion = new EllipsoidTissueRegion() { Dx = 1.0, Dy = 2.0 } + EllipsoidRegion = new EllipsoidTissueRegion { Dx = 1.0, Dy = 2.0 } }, DetectorInputs = new List { new ROfFxDetectorInput() } }; + // set to catch Console output + var output = new StringWriter(); + Console.SetOut(output); var result = SimulationInputValidation.ValidateInput(input); - Assert.IsFalse(result.IsValid); + Assert.IsTrue(result.IsValid); // only warning + Assert.That(output.ToString(), Is.EqualTo("Warning: R(fx) theory assumes a homogeneous or layered tissue geometry: user discretion advised\r\n")); } + + /// + /// Test to verify input with voxel in tissue and R(fx) detector outputs warning + /// but continues as valid input + /// [Test] - public void validate_voxel_tissue_and_ROfFx_detectors_are_not_defined_together() + public void Validate_voxel_tissue_and_ROfFx_detectors_defined_together_issues_warning() { // generate input embedded ellipsoid tissue and cylindrical detector - var input = new SimulationInput() + var input = new SimulationInput { - TissueInput = new SingleVoxelTissueInput() + TissueInput = new SingleVoxelTissueInput { - VoxelRegion = new VoxelTissueRegion() + // make sure voxel defined so validation doesn't error on voxel geometry + VoxelRegion = new VoxelTissueRegion + { + X = new DoubleRange(-5,5,2), + Y = new DoubleRange(-5,5,2), + Z = new DoubleRange(1,6,2) + } }, DetectorInputs = new List { new ROfFxDetectorInput() } }; + // set to catch Console output + var output = new StringWriter(); + Console.SetOut(output); var result = SimulationInputValidation.ValidateInput(input); - Assert.IsFalse(result.IsValid); + Assert.IsTrue(result.IsValid); // only warning + Assert.That(output.ToString(), Is.EqualTo("Warning: R(fx) theory assumes a homogeneous or layered tissue geometry: user discretion advised\r\n")); } + + /// + /// Test to verify input with negative optical properties is invalid + /// [Test] - public void validate_tissue_optical_properties_are_non_negative() + public void Validate_tissue_optical_properties_are_non_negative() { // generate input embedded ellipsoid tissue and cylindrical detector - var input = new SimulationInput() + var input = new SimulationInput { TissueInput = new MultiLayerTissueInput( new ITissueRegion[] @@ -157,5 +330,143 @@ public void validate_tissue_optical_properties_are_non_negative() var result = SimulationInputValidation.ValidateInput(input); Assert.IsFalse(result.IsValid); } + + /// + /// Test to verify blood volume detectors + /// + [Test] + public void Validate_blood_volume_detectors() + { + // generate multilayer tissue + var input = new SimulationInput + { + TissueInput = new MultiLayerTissueInput( + new ITissueRegion[] + { + new LayerTissueRegion( + new DoubleRange(double.NegativeInfinity, 0.0), + new OpticalProperties(0.0, 1e-10, 1.0, 1.0)), + new LayerTissueRegion( + new DoubleRange(0.0, 1.0), + new OpticalProperties(0.01, 1.0, 0.8, 1.4)), + new LayerTissueRegion( + new DoubleRange(1.0, 10.0), + new OpticalProperties(0.01, 1.0, 0.8, 1.4)), + new LayerTissueRegion( + new DoubleRange(10.0, double.PositiveInfinity), + new OpticalProperties(0.0, 1e-10, 1.0, 1.0)) + } + ), + DetectorInputs = new List + { + new ReflectedDynamicMTOfRhoAndSubregionHistDetectorInput + { + Rho=new DoubleRange(0.0, 10.0, 21), // rho bins MAKE SURE AGREES with ROfRho rho specification for unit test below + Z = new DoubleRange(0.0, 10.0, 21), + MTBins=new DoubleRange(0.0, 500.0, 51), // MT bins + FractionalMTBins = new DoubleRange(0.0, 1.0, 11), + BloodVolumeFraction = new List { 0, 0.5, 0.5, 0}, + TallySecondMoment = true + }, + new TransmittedDynamicMTOfRhoAndSubregionHistDetectorInput + { + Rho=new DoubleRange(0.0, 10.0, 21), // rho bins MAKE SURE AGREES with TOfRho rho specification for unit test below + Z = new DoubleRange(0.0, 10.0, 21), + MTBins=new DoubleRange(0.0, 500.0, 51), // MT bins + FractionalMTBins = new DoubleRange(0.0, 1.0, 11), + BloodVolumeFraction = new List { 0, 0.5, 0.5, 0 }, + TallySecondMoment = true + }, + new ReflectedDynamicMTOfXAndYAndSubregionHistDetectorInput + { + X = new DoubleRange(-10.0, 10.0, 21), + Y = new DoubleRange(-10.0, 10.0, 21), + Z = new DoubleRange(0.0, 10.0, 21), + MTBins=new DoubleRange(0.0, 500.0, 51), // MT bins + FractionalMTBins = new DoubleRange(0.0, 1.0, 11), + BloodVolumeFraction = new List { 0, 0.5, 0.5, 0}, + TallySecondMoment = true + }, + new TransmittedDynamicMTOfXAndYAndSubregionHistDetectorInput + { + X = new DoubleRange(-10.0, 10.0, 21), + Y = new DoubleRange(-10.0, 10.0, 21), + Z = new DoubleRange(0.0, 10.0, 21), + MTBins=new DoubleRange(0.0, 500.0, 51), // MT bins + FractionalMTBins = new DoubleRange(0.0, 1.0, 11), + BloodVolumeFraction = new List { 0, 0.5, 0.5, 0}, + TallySecondMoment = true + } + } + }; + // verify when blood volume fraction size is same as tissue + var result = SimulationInputValidation.ValidateInput(input); + Assert.IsTrue(result.IsValid); + // create case when validation fails + input = new SimulationInput + { + TissueInput = new MultiLayerTissueInput( + new ITissueRegion[] + { + new LayerTissueRegion( + new DoubleRange(double.NegativeInfinity, 0.0), + new OpticalProperties(0.0, 1e-10, 1.0, 1.0)), + new LayerTissueRegion( + new DoubleRange(0.0, 1.0), + new OpticalProperties(0.01, 1.0, 0.8, 1.4)), + new LayerTissueRegion( + new DoubleRange(1.0, 10.0), + new OpticalProperties(0.01, 1.0, 0.8, 1.4)), + new LayerTissueRegion( + new DoubleRange(10.0, double.PositiveInfinity), + new OpticalProperties(0.0, 1e-10, 1.0, 1.0)) + } + ), + DetectorInputs = new List + { + new ReflectedDynamicMTOfRhoAndSubregionHistDetectorInput + { + Rho=new DoubleRange(0.0, 10.0, 21), // rho bins MAKE SURE AGREES with ROfRho rho specification for unit test below + Z = new DoubleRange(0.0, 10.0, 21), + MTBins=new DoubleRange(0.0, 500.0, 51), // MT bins + FractionalMTBins = new DoubleRange(0.0, 1.0, 11), + BloodVolumeFraction = new List { 0, 0.5, 0}, // FAILURE + TallySecondMoment = true + }, + new TransmittedDynamicMTOfRhoAndSubregionHistDetectorInput + { + Rho=new DoubleRange(0.0, 10.0, 21), // rho bins MAKE SURE AGREES with TOfRho rho specification for unit test below + Z = new DoubleRange(0.0, 10.0, 21), + MTBins=new DoubleRange(0.0, 500.0, 51), // MT bins + FractionalMTBins = new DoubleRange(0.0, 1.0, 11), + BloodVolumeFraction = new List { 0, 0.5, 0.5, 0 }, + TallySecondMoment = true + }, + new ReflectedDynamicMTOfXAndYAndSubregionHistDetectorInput + { + X = new DoubleRange(-10.0, 10.0, 21), + Y = new DoubleRange(-10.0, 10.0, 21), + Z = new DoubleRange(0.0, 10.0, 21), + MTBins=new DoubleRange(0.0, 500.0, 51), // MT bins + FractionalMTBins = new DoubleRange(0.0, 1.0, 11), + BloodVolumeFraction = new List { 0, 0.5, 0.5, 0}, + TallySecondMoment = true + }, + new TransmittedDynamicMTOfXAndYAndSubregionHistDetectorInput + { + X = new DoubleRange(-10.0, 10.0, 21), + Y = new DoubleRange(-10.0, 10.0, 21), + Z = new DoubleRange(0.0, 10.0, 21), + MTBins=new DoubleRange(0.0, 500.0, 51), // MT bins + FractionalMTBins = new DoubleRange(0.0, 1.0, 11), + BloodVolumeFraction = new List { 0, 0.5, 0.5, 0}, + TallySecondMoment = true + } + } + }; + // verify when blood volume fraction size is same as tissue + result = SimulationInputValidation.ValidateInput(input); + Assert.IsFalse(result.IsValid); + } } } diff --git a/src/Vts/MonteCarlo/DataStructuresValidation/SimulationInputValidation.cs b/src/Vts/MonteCarlo/DataStructuresValidation/SimulationInputValidation.cs index 7ae39437..1a0e9803 100644 --- a/src/Vts/MonteCarlo/DataStructuresValidation/SimulationInputValidation.cs +++ b/src/Vts/MonteCarlo/DataStructuresValidation/SimulationInputValidation.cs @@ -2,7 +2,6 @@ using System.Linq; using Vts.Common; using Vts.MonteCarlo.DataStructuresValidation; -using Vts.MonteCarlo.Interfaces; using Vts.MonteCarlo.Sources; using Vts.MonteCarlo.Tissues; @@ -11,7 +10,7 @@ namespace Vts.MonteCarlo /// /// This sanity checks SimulationInput /// - public class SimulationInputValidation + public static class SimulationInputValidation { /// /// Master of call validation methods. Calls methods to validate source, @@ -28,16 +27,14 @@ public static ValidationResult ValidateInput(SimulationInput input) si => ValidateTissueInput(si.TissueInput), ValidateDetectorInput, ValidateCombinedInputParameters, - ValidateCurrentIncapabilities + ValidateCurrentCapabilities }; foreach (var validation in validations) { var tempResult = validation(input); - if (!tempResult.IsValid) - { - return tempResult; - } + if (!tempResult.IsValid) return tempResult; + } return new ValidationResult( true, "Simulation input is valid"); @@ -116,8 +113,8 @@ private static ValidationResult ValidateDetectorInput(SimulationInput si) { return new ValidationResult( false, - "DetectorInput not implemented yet:" + detectorInput.ToString(), - "Please omit " + detectorInput.ToString() + " from DetectorInput list"); + "DetectorInput not implemented yet:" + detectorInput, + "Please omit " + detectorInput + " from DetectorInput list"); } } // make sure all detectors have unique Names @@ -137,7 +134,12 @@ private static ValidationResult ValidateDetectorInput(SimulationInput si) /// /// This method checks the input against combined combinations of options - /// and source, tissue, detector definitions. + /// and source, tissue, detector definitions. The philosophy here is that if the transport will + /// not error, a warning is issued and the validation result remains true. This allows users to + /// specify inconsistent combinations, e.g. angled source and cylindrical coordinate detectors, + /// receive a warning and have the simulation proceed. However, if the transport will error then + /// the validation result will be false, the validationRule and remarks output and simulation stops, + /// e.g. embedded ellipsoid in tissue that overlaps tissue layer. /// /// input to be validated /// An instance of ValidationResult with IsValid set and error message if false @@ -145,7 +147,7 @@ private static ValidationResult ValidateCombinedInputParameters(SimulationInput { // check that absorption weighting type set to analog and RR weight threshold != 0.0 if (input.Options.AbsorptionWeightingType == AbsorptionWeightingType.Analog && - input.Options.RussianRouletteWeightThreshold != 0.0) + input.Options.RussianRouletteWeightThreshold > 0.0) { return new ValidationResult( false, @@ -153,6 +155,37 @@ private static ValidationResult ValidateCombinedInputParameters(SimulationInput "With Analog absorption weighting, set Russian Roulette weight threshold = 0.0"); } + // check combination of tissue definition with detector definition + var tempResult = ValidateTissueCombinedWithDetectors(input); + if (!tempResult.IsValid) return tempResult; + + // check combination of source definition with detector definition + if (input.SourceInput is DirectionalPointSourceInput source && + source.Direction != new Direction(0,0,1) && + input.DetectorInputs.Any(detectorInput => detectorInput.TallyDetails.IsCylindricalTally)) + { + Console.WriteLine("Warning: Angled source and cylindrical coordinate detector defined: user discretion advised"); + return new ValidationResult( + true, + "Warning: Angled source and cylindrical coordinate detector defined", + "User discretion advised: change detector to Cartesian equivalent or define source to be normal"); + } + if (input.DetectorInputs.Where(detectorInput => detectorInput.TallyDetails.IsTransmittanceTally && + input.TissueInput is MultiLayerTissueInput).Any(detectorInput => ((dynamic)detectorInput).FinalTissueRegionIndex == 0)) + { + return new ValidationResult( + false, + "Transmittance detectors with MultiLayerTissues cannot detect in tissue region 0", + "Change FinalTissueRegionIndex to be index of air below tissue (index >= 2)"); + } + return new ValidationResult( + true, + "Input options or tissue/detector combinations are valid", + ""); + } + + private static ValidationResult ValidateTissueCombinedWithDetectors(SimulationInput input) + { switch (input.TissueInput) { // check that if single ellipsoid tissue specified and (r,z) detector specified, @@ -160,98 +193,93 @@ private static ValidationResult ValidateCombinedInputParameters(SimulationInput case SingleEllipsoidTissueInput tissueWithEllipsoid: { var ellipsoid = (EllipsoidTissueRegion)tissueWithEllipsoid.EllipsoidRegion; - foreach (var detectorInput in input.DetectorInputs) - { - switch (detectorInput.TallyDetails.IsCylindricalTally) - { - case true when - ellipsoid.Center.X != 0.0 && ellipsoid.Center.Y != 0.0: - return new ValidationResult( - false, - "Ellipsoid must be centered at (x,y)=(0,0) for cylindrical tallies", - "Change ellipsoid center to (0,0) or specify non-cylindrical type tally"); - case true when ellipsoid.Dx != ellipsoid.Dy: - return new ValidationResult( - false, - "Ellipsoid must have Dx=Dy for cylindrical tallies", - "Change ellipsoid.Dx to be = to Dy or specify non-cylindrical type tally"); - } - - if (detectorInput.TallyType == TallyType.ROfFx) - { - return new ValidationResult( - false, - "R(fx) tallies assume a homogeneous or layered tissue geometry", - "Change tissue type to be homogeneous or layered"); - } - } - + var tempResult = ValidateSingleEllipsoidTissueCombinedWithDetectors(input, ellipsoid); + if (!tempResult.IsValid) return tempResult; break; } // check that if single voxel or single infinite cylinder tissue specified, // cannot specify (r,z) detector case SingleVoxelTissueInput: case SingleInfiniteCylinderTissueInput: - { - foreach (var detectorInput in input.DetectorInputs) { - if (detectorInput.TallyDetails.IsCylindricalTally) - { - return new ValidationResult( - false, - "Cannot use Single Voxel Tissue for cylindrical tallies", - "Change detector inputs to specify non-cylindrical type tallies"); - } - if (detectorInput.TallyType == TallyType.ROfFx) + foreach (var detectorInput in input.DetectorInputs) { + if (detectorInput.TallyDetails.IsCylindricalTally) + { + Console.WriteLine("Warning: voxel in tissue with cylindrical detector defined: user discretion advised"); + return new ValidationResult( + true, + "Warning: voxel in tissue with cylindrical detector defined", + "User discretion advised: change detector inputs to specify non-cylindrical type tallies"); + } + + if (detectorInput.TallyType != TallyType.ROfFx) continue; + Console.WriteLine("Warning: R(fx) theory assumes a homogeneous or layered tissue geometry: user discretion advised"); return new ValidationResult( - false, - "R(fx) tallies assume a homogeneous or layered tissue geometry", - "Change tissue type to be homogeneous or layered"); + true, + "Warning: R(fx) theory assumes a homogeneous or layered tissue geometry", + "User discretion advised"); } + break; } - - break; - } // check that if bounding volume tissue specified, the ATotalBoundingVolumeTissueInput detector needs // to be specified case BoundingCylinderTissueInput when input.DetectorInputs.All(d => d.TallyType != TallyType.ATotalBoundingVolume): return new ValidationResult( - false, - "BoundingCylinderTissueInput needs associated detector ATotalBoundingVolume to be defined", - "Add ATotalBoundingVolumeDetectorInput to detector inputs"); - } - - if (input.SourceInput is DirectionalPointSourceInput source && - source.Direction != new Direction(0,0,1) && - input.DetectorInputs.Any(detectorInput => detectorInput.TallyDetails.IsCylindricalTally)) - { - return new ValidationResult( false, - "If source is angled, cannot define cylindrically symmetric detectors", - "Change detector to Cartesian equivalent or define source to be normal"); + "BoundingCylinderTissueInput needs associated detector ATotalBoundingVolume to be defined", + "Add ATotalBoundingVolumeDetectorInput to detector inputs"); } - if (input.DetectorInputs.Where(detectorInput => detectorInput.TallyDetails.IsTransmittanceTally && - input.TissueInput is MultiLayerTissueInput).Any(detectorInput => ((dynamic)detectorInput).FinalTissueRegionIndex == 0)) + return new ValidationResult( + true, + "Input options or tissue/detector combinations are valid", + ""); + } + + private static ValidationResult ValidateSingleEllipsoidTissueCombinedWithDetectors( + SimulationInput input, EllipsoidTissueRegion ellipsoid) + { + foreach (var detectorInput in input.DetectorInputs) { + switch (detectorInput.TallyDetails.IsCylindricalTally) + { + // check if ellipsoid off center, then not cylindrically symmetric, continue with warning + case true when + Math.Abs(ellipsoid.Center.X) > 1e-6 || Math.Abs(ellipsoid.Center.Y) > 1e-6: + Console.WriteLine("Warning: off center ellipsoid in tissue with cylindrical detector defined: user discretion advised"); + return new ValidationResult( + true, + "Warning: off center ellipsoid in tissue with cylindrical detector defined", + "User discretion advised: change ellipsoid center to (0,0) or specify non-cylindrical type tally"); + // check if Dx != Dy, then not cylindrically symmetric, continue with warning + case true when Math.Abs(ellipsoid.Dx - ellipsoid.Dy) > 1e-6: + Console.WriteLine("Warning: ellipsoid with Dx != Dy in tissue with cylindrical detector defined: user discretion advised"); + return new ValidationResult( + true, + "Warning: ellipsoid with Dx != Dy in tissue with cylindrical detector defined", + "User discretion advised: change ellipsoid.Dx to be = to Dy or specify non-cylindrical type tally"); + } + + // theory assumes homogeneous tissue for R(fx), however users can continue with warning + if (detectorInput.TallyType != TallyType.ROfFx) continue; + Console.WriteLine("Warning: R(fx) theory assumes a homogeneous or layered tissue geometry: user discretion advised"); return new ValidationResult( - false, - "Transmittance detectors with MultiLayerTissues cannot detect in tissue region 0", - "Change FinalTissueRegionIndex to be index of air below tissue (index >= 2)"); + true, + "Warning: R(fx) theory assumes a homogeneous or layered tissue geometry", + "User discretion advised"); } return new ValidationResult( true, "Input options or tissue/detector combinations are valid", ""); - } /// - /// Method checks SimulationInput against current in-capabilities of the code. + /// Method checks SimulationInput against current capabilities of the code. /// /// SimulationInput /// An instance of the ValidationResult class - private static ValidationResult ValidateCurrentIncapabilities(SimulationInput input) + private static ValidationResult ValidateCurrentCapabilities(SimulationInput input) { switch (input.Options.AbsorptionWeightingType) { @@ -278,7 +306,18 @@ private static ValidationResult ValidateCurrentIncapabilities(SimulationInput in default: throw new ArgumentOutOfRangeException(typeof(AbsorptionWeightingType).ToString()); } + // check current detector capabilities + var tempResult = ValidateCurrentDetectorCapabilities(input); + if (!tempResult.IsValid) return tempResult; + return new ValidationResult( + true, + "Detector definitions are consistent with current capabilities"); + } + + private static ValidationResult ValidateCurrentDetectorCapabilities( + SimulationInput input) + { foreach (var detectorInput in input.DetectorInputs) { // can only run dMC detectors with 1 perturbed region for the present @@ -290,23 +329,12 @@ private static ValidationResult ValidateCurrentIncapabilities(SimulationInput in { return dMCdROfRhodMusDetectorInputValidation.ValidateInput(detectorInput); } + // check that number in blood volume list matches number of tissue subregions - if (detectorInput.TallyType.Contains("ReflectedDynamicMTOfRhoAndSubregionHist")) - { - return ReflectedDynamicMTOfRhoAndSubregionHistDetectorInputValidation.ValidateInput(detectorInput, input.TissueInput.Regions.Count()); - } - if (detectorInput.TallyType.Contains("ReflectedDynamicMTOfXAndYAndSubregionHist")) - { - return ReflectedDynamicMTOfXAndYAndSubregionHistDetectorInputValidation.ValidateInput(detectorInput, input.TissueInput.Regions.Count()); - } - if (detectorInput.TallyType.Contains("TransmittedDynamicMTOfRhoAndSubregionHist")) - { - return TransmittedDynamicMTOfRhoAndSubregionHistDetectorInputValidation.ValidateInput(detectorInput, input.TissueInput.Regions.Count()); - } - if (detectorInput.TallyType.Contains("TransmittedDynamicMTOfXAndYAndSubregionHist")) - { - return TransmittedDynamicMTOfXAndYAndSubregionHistDetectorInputValidation.ValidateInput(detectorInput, input.TissueInput.Regions.Count()); - } + var tempResult = ValidateBloodVolumeDetectorConsistencies(input, detectorInput); + if (!tempResult.IsValid) return tempResult; + + // check fiber consistencies if (detectorInput.TallyType.Contains("SurfaceFiber")) { return SurfaceFiberDetectorInputValidation.ValidateInput(detectorInput); @@ -321,10 +349,39 @@ private static ValidationResult ValidateCurrentIncapabilities(SimulationInput in { return RecessedDetectorInputValidation.ValidateInput(detectorInput); } - } + } return new ValidationResult( true, "Detector definitions are consistent with current capabilities"); } + + private static ValidationResult ValidateBloodVolumeDetectorConsistencies( + SimulationInput input, IDetectorInput detectorInput) + { + if (detectorInput.TallyType.Contains("ReflectedDynamicMTOfRhoAndSubregionHist")) + { + return ReflectedDynamicMTOfRhoAndSubregionHistDetectorInputValidation.ValidateInput( + detectorInput, input.TissueInput.Regions.Length); + } + if (detectorInput.TallyType.Contains("ReflectedDynamicMTOfXAndYAndSubregionHist")) + { + return ReflectedDynamicMTOfXAndYAndSubregionHistDetectorInputValidation.ValidateInput( + detectorInput, input.TissueInput.Regions.Length); + } + if (detectorInput.TallyType.Contains("TransmittedDynamicMTOfRhoAndSubregionHist")) + { + return TransmittedDynamicMTOfRhoAndSubregionHistDetectorInputValidation.ValidateInput( + detectorInput, input.TissueInput.Regions.Length); + } + if (detectorInput.TallyType.Contains("TransmittedDynamicMTOfXAndYAndSubregionHist")) + { + return TransmittedDynamicMTOfXAndYAndSubregionHistDetectorInputValidation.ValidateInput( + detectorInput, input.TissueInput.Regions.Length); + } + return new ValidationResult( + true, + "Detector definitions are consistent with current capabilities"); + + } } }