From 57148d879153c7ca5054abf6e904f66bf74e47cb Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Tue, 17 Sep 2024 11:49:59 -0700 Subject: [PATCH] Fix VRI Utilization output --- .../application/VdypStartApplication.java | 7 +- .../nrs/vdyp/io/write/VdypOutputWriter.java | 37 +++- .../vdyp/io/ZipOutputFileResolverTest.java | 4 +- .../vdyp/io/write/VdypOutputWriterTest.java | 26 ++- .../java/ca/bc/gov/nrs/vdyp/fip/FipStart.java | 15 ++ .../bc/gov/nrs/vdyp/vri/model/VriLayer.java | 2 +- .../gov/nrs/vdyp/vri/ParsersTogetherTest.java | 5 +- .../ca/bc/gov/nrs/vdyp/vri/VriStartTest.java | 204 ------------------ 8 files changed, 74 insertions(+), 226 deletions(-) diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypStartApplication.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypStartApplication.java index f0e2fe11b..5eecf2f08 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypStartApplication.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypStartApplication.java @@ -225,7 +225,12 @@ public void init(FileSystemFileResolver resolver, Map controlMap setControlMap(controlMap); closeVriWriter(); - vriWriter = new VdypOutputWriter(controlMap, resolver); + vriWriter = createWriter(resolver, controlMap); + } + + protected VdypOutputWriter createWriter(FileSystemFileResolver resolver, Map controlMap) + throws IOException { + return new VdypOutputWriter(controlMap, resolver); } protected abstract BaseControlParser getControlFileParser(); diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/write/VdypOutputWriter.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/write/VdypOutputWriter.java index 6a4b2936d..560c4b430 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/write/VdypOutputWriter.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/write/VdypOutputWriter.java @@ -206,6 +206,18 @@ void writeSpecies(VdypLayer layer, VdypSpecies spec) throws IOException { } + protected float fractionForest(VdypPolygon polygon, VdypLayer layer) { + return polygon.getPercentAvailable() / 100f; + } + + float safeMultiply(float value, float factor) { + if (value <= 0) { + return value; + } + + return value * factor; + } + /** * Write the utilization records for a layer or species to the utilization file. * @@ -214,7 +226,7 @@ void writeSpecies(VdypLayer layer, VdypSpecies spec) throws IOException { * @throws IOException */ // V7W_AIU Internalized loop over utilization classes - void writeUtilization(VdypLayer layer, VdypUtilizationHolder utils) throws IOException { + void writeUtilization(VdypPolygon polygon, VdypLayer layer, VdypUtilizationHolder utils) throws IOException { Optional specId = Optional.empty(); Optional specIndex = Optional.empty(); if (utils instanceof VdypSpecies spec) { @@ -222,6 +234,8 @@ void writeUtilization(VdypLayer layer, VdypUtilizationHolder utils) throws IOExc specIndex = Optional.of(spec.getGenusIndex()); } + float fractionForest = fractionForest(polygon, layer); + for (var uc : UtilizationClass.values()) { Optional height = Optional.empty(); if (uc.index < 1) { @@ -249,15 +263,18 @@ void writeUtilization(VdypLayer layer, VdypUtilizationHolder utils) throws IOExc uc.index, - utils.getBaseAreaByUtilization().getCoe(uc.index), // - utils.getTreesPerHectareByUtilization().getCoe(uc.index), // + utils.getBaseAreaByUtilization().getCoe(uc.index) * fractionForest, // + utils.getTreesPerHectareByUtilization().getCoe(uc.index) * fractionForest, // height.orElse(EMPTY_FLOAT), // - utils.getWholeStemVolumeByUtilization().getCoe(uc.index), // - utils.getCloseUtilizationVolumeByUtilization().getCoe(uc.index), // - utils.getCloseUtilizationVolumeNetOfDecayByUtilization().getCoe(uc.index), // - utils.getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization().getCoe(uc.index), // - utils.getCloseUtilizationVolumeNetOfDecayWasteAndBreakageByUtilization().getCoe(uc.index), // + utils.getWholeStemVolumeByUtilization().getCoe(uc.index) * fractionForest, // + utils.getCloseUtilizationVolumeByUtilization().getCoe(uc.index) * fractionForest, // + utils.getCloseUtilizationVolumeNetOfDecayByUtilization().getCoe(uc.index) * fractionForest, // + utils.getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization().getCoe(uc.index) * fractionForest, // + safeMultiply( + utils.getCloseUtilizationVolumeNetOfDecayWasteAndBreakageByUtilization().getCoe(uc.index), + fractionForest + ), // quadMeanDiameter.orElse(layer.getLayerType() == LayerType.PRIMARY ? // EMPTY_FLOAT : 0f @@ -277,13 +294,13 @@ public void writePolygonWithSpeciesAndUtilization(VdypPolygon polygon) throws IO writePolygon(polygon); for (var layer : polygon.getLayers().values()) { - writeUtilization(layer, layer); + writeUtilization(polygon, layer, layer); List specs = new ArrayList<>(layer.getSpecies().size()); specs.addAll(layer.getSpecies().values()); specs.sort(Utils.compareUsing(BaseVdypSpecies::getGenus)); for (var species : specs) { writeSpecies(layer, species); - writeUtilization(layer, species); + writeUtilization(polygon, layer, species); } } writeSpeciesEndRecord(polygon); diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/ZipOutputFileResolverTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/ZipOutputFileResolverTest.java index f139bc394..d095ccc15 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/ZipOutputFileResolverTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/ZipOutputFileResolverTest.java @@ -27,11 +27,11 @@ void testZipOutputFileResolver() throws IOException { ZipOutputFileResolver resolver = new ZipOutputFileResolver(); - MatcherAssert.assertThat(resolver.toPath("file").toString(), Matchers.endsWith("vdyp-lib/vdyp-common/file")); + MatcherAssert.assertThat(resolver.toPath("file").toString(), Matchers.endsWith("lib/vdyp-common/file")); assertThrows(UnsupportedOperationException.class, () -> resolver.resolveForInput("file")); - MatcherAssert.assertThat(resolver.toString("file"), Matchers.endsWith("vdyp-lib/vdyp-common/file")); + MatcherAssert.assertThat(resolver.toString("file"), Matchers.endsWith("lib/vdyp-common/file")); for (int i = 0; i < 5; i++) { OutputStream os = resolver.resolveForOutput("file" + i); diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/write/VdypOutputWriterTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/write/VdypOutputWriterTest.java index fd7fcc09f..346bff98e 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/write/VdypOutputWriterTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/write/VdypOutputWriterTest.java @@ -173,8 +173,15 @@ void testWriteSpecies() throws IOException { void testWriteUtilizationForLayer() throws IOException { try (var unit = new VdypOutputWriter(controlMap, fileResolver);) { - var layer = VdypLayer.build(builder -> { + var polygon = VdypPolygon.build(builder -> { builder.polygonIdentifier("082E004 615 1988"); + + builder.biogeoclimaticZone(Utils.getBec("IDF", controlMap)); + builder.forestInventoryZone("D"); + + builder.percentAvailable(100f); + }); + var layer = VdypLayer.build(polygon, builder -> { builder.layerType(LayerType.PRIMARY); builder.addSpecies(specBuilder -> { @@ -233,7 +240,7 @@ void testWriteUtilizationForLayer() throws IOException { // Should be ignored and computed from BA and TPH layer.setQuadraticMeanDiameterByUtilization(Utils.utilizationVector(4f, 4f, 4f, 4f, 4f, 4f)); - unit.writeUtilization(layer, layer); + unit.writeUtilization(polygon, layer, layer); } utilStream.assertContent( VdypMatchers.hasLines( @@ -253,8 +260,15 @@ void testWriteUtilizationForLayer() throws IOException { void testWriteUtilizationZeroBaseArea() throws IOException { try (var unit = new VdypOutputWriter(controlMap, fileResolver);) { - var layer = VdypLayer.build(builder -> { + var polygon = VdypPolygon.build(builder -> { builder.polygonIdentifier("082E004 615 1988"); + + builder.biogeoclimaticZone(Utils.getBec("IDF", controlMap)); + builder.forestInventoryZone("D"); + + builder.percentAvailable(100f); + }); + var layer = VdypLayer.build(polygon, builder -> { builder.layerType(LayerType.PRIMARY); builder.addSpecies(specBuilder -> { @@ -311,7 +325,7 @@ void testWriteUtilizationZeroBaseArea() throws IOException { // Should be ignored and computed from BA and TPH species.setQuadraticMeanDiameterByUtilization(Utils.utilizationVector(4f, 4f, 4f, 4f, 4f, 4f)); - unit.writeUtilization(layer, species); + unit.writeUtilization(polygon, layer, species); } utilStream.assertContent( VdypMatchers.hasLines( @@ -335,7 +349,7 @@ void testWritePolygonWithChildren() throws IOException { VdypPolygon polygon = VdypPolygon.build(builder -> { builder.polygonIdentifier("082E004 615 1988"); - builder.percentAvailable(90f); + builder.percentAvailable(100f); builder.biogeoclimaticZone(Utils.getBec("IDF", controlMap)); builder.forestInventoryZone("D"); builder.mode(PolygonMode.START); @@ -427,7 +441,7 @@ void testWritePolygonWithChildren() throws IOException { unit.writePolygonWithSpeciesAndUtilization(polygon); } - polyStream.assertContent(is("082E004 615 1988 IDF D 90 28119 1\n")); + polyStream.assertContent(is("082E004 615 1988 IDF D 100 28119 1\n")); utilStream.assertContent( VdypMatchers.hasLines( "082E004 615 1988 P 0 -1 0.02865 9.29 7.8377 0.1077 0.0000 0.0000 0.0000 0.0000 6.3", // diff --git a/lib/vdyp-fip/src/main/java/ca/bc/gov/nrs/vdyp/fip/FipStart.java b/lib/vdyp-fip/src/main/java/ca/bc/gov/nrs/vdyp/fip/FipStart.java index 49cacc88e..ebcb840bd 100644 --- a/lib/vdyp-fip/src/main/java/ca/bc/gov/nrs/vdyp/fip/FipStart.java +++ b/lib/vdyp-fip/src/main/java/ca/bc/gov/nrs/vdyp/fip/FipStart.java @@ -54,9 +54,11 @@ import ca.bc.gov.nrs.vdyp.fip.model.FipPolygon; import ca.bc.gov.nrs.vdyp.fip.model.FipSite; import ca.bc.gov.nrs.vdyp.fip.model.FipSpecies; +import ca.bc.gov.nrs.vdyp.io.FileSystemFileResolver; import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; import ca.bc.gov.nrs.vdyp.io.parse.control.BaseControlParser; import ca.bc.gov.nrs.vdyp.io.parse.streaming.StreamingParser; +import ca.bc.gov.nrs.vdyp.io.write.VdypOutputWriter; import ca.bc.gov.nrs.vdyp.model.BaseVdypSpecies; import ca.bc.gov.nrs.vdyp.model.BecDefinition; import ca.bc.gov.nrs.vdyp.model.Coefficients; @@ -91,6 +93,19 @@ public static void main(final String... args) throws IOException { } } + @Override + protected VdypOutputWriter createWriter(FileSystemFileResolver resolver, Map controlMap) + throws IOException { + return new VdypOutputWriter(controlMap, resolver) { + + @Override + protected float fractionForest(VdypPolygon polygon, VdypLayer layer) { + return 1f; + } + + }; + } + // FIP_SUB // TODO Fortran takes a vector of flags (FIPPASS) controlling which stages are // implemented. FIPSTART always uses the same vector so far now that's not diff --git a/lib/vdyp-vri/src/main/java/ca/bc/gov/nrs/vdyp/vri/model/VriLayer.java b/lib/vdyp-vri/src/main/java/ca/bc/gov/nrs/vdyp/vri/model/VriLayer.java index 1618021a1..8c5a5e830 100644 --- a/lib/vdyp-vri/src/main/java/ca/bc/gov/nrs/vdyp/vri/model/VriLayer.java +++ b/lib/vdyp-vri/src/main/java/ca/bc/gov/nrs/vdyp/vri/model/VriLayer.java @@ -213,7 +213,7 @@ protected void check(Collection errors) { @Override protected VriLayer doBuild() { - float multiplier = percentAvailable.orElse(100f) / 100f; + float multiplier = 100f / percentAvailable.orElse(100f); VriLayer result = new VriLayer( polygonIdentifier.get(), // layerType.get(), // diff --git a/lib/vdyp-vri/src/test/java/ca/bc/gov/nrs/vdyp/vri/ParsersTogetherTest.java b/lib/vdyp-vri/src/test/java/ca/bc/gov/nrs/vdyp/vri/ParsersTogetherTest.java index 91507a19e..68a051250 100644 --- a/lib/vdyp-vri/src/test/java/ca/bc/gov/nrs/vdyp/vri/ParsersTogetherTest.java +++ b/lib/vdyp-vri/src/test/java/ca/bc/gov/nrs/vdyp/vri/ParsersTogetherTest.java @@ -363,9 +363,10 @@ primaryResult, allOf( hasProperty("crownClosure", is(95f)), // hasProperty("utilization", is(9f)), hasProperty( "baseArea", // - present(closeTo(20f * 0.75f)) + present(closeTo(20f * (1f / 0.75f))) ), // Apply Layer Percent Available - hasProperty("treesPerHectare", present(closeTo(300f * 0.75f))) // Apply Layer Percent Available + hasProperty("treesPerHectare", present(closeTo(300f * (1f / 0.75f)))) // Apply Layer Percent + // Available ) ); diff --git a/lib/vdyp-vri/src/test/java/ca/bc/gov/nrs/vdyp/vri/VriStartTest.java b/lib/vdyp-vri/src/test/java/ca/bc/gov/nrs/vdyp/vri/VriStartTest.java index c4cbba627..42fc515be 100644 --- a/lib/vdyp-vri/src/test/java/ca/bc/gov/nrs/vdyp/vri/VriStartTest.java +++ b/lib/vdyp-vri/src/test/java/ca/bc/gov/nrs/vdyp/vri/VriStartTest.java @@ -2444,210 +2444,6 @@ primaryLayer, hasProperty("loreyHeightByUtilization", utilizationHeight(5.457702 app.close(); } - @Test - void testProcessPrimary2() throws Exception { - - controlMap = TestUtils.loadControlMap(); - - VriStart app = new VriStart(); - - MockFileResolver resolver = dummyInput(); - - var poly = VriPolygon.build(pb -> { - pb.polygonIdentifier("TestPoly", 2024); - pb.biogeoclimaticZone(Utils.getBec("IDF", controlMap)); - pb.yieldFactor(1.0f); - pb.forestInventoryZone("G"); - pb.percentAvailable(85); // Default - pb.addLayer(lb -> { - lb.layerType(LayerType.PRIMARY); - lb.crownClosure(57.8f); - lb.utilization(7.5f); - lb.baseArea(77.6470566f); - lb.treesPerHectare(1000f); - lb.utilization(7.5f); - - lb.inventoryTypeGroup(14); - lb.empiricalRelationshipParameterIndex(76); - - lb.primaryGenus("H"); - // 1 3 - lb.addSpecies(sb -> { - sb.genus("B", controlMap); - sb.percentGenus(3); - sb.addSp64Distribution("BL", 100); - sb.addSite(ib -> { - ib.siteSpecies("BL"); - ib.siteCurveNumber(8); - }); - }); - - // 2 4 (Primary) - lb.addSpecies(sb -> { - sb.genus("C", controlMap); - sb.percentGenus(30); - sb.addSp64Distribution("CW", 100); - sb.addSite(ib -> { - ib.siteCurveNumber(11); - ib.ageTotal(200); - ib.height(28f); - ib.siteIndex(14.3f); - ib.yearsToBreastHeight(10.9f); - ib.breastHeightAge(189.1f); - ib.ageTotal(100f); - ib.siteSpecies("CW"); - }); - }); - - // 3 8 - lb.addSpecies(sb -> { - sb.genus("H", controlMap); - sb.percentGenus(48.9f); - sb.addSp64Distribution("HW", 100); - sb.addSite(ib -> { - ib.siteCurveNumber(37); - ib.height(32f); - ib.siteIndex(14.6f); - ib.yearsToBreastHeight(9.7f); - ib.breastHeightAge(190.3f); - ib.ageTotal(200f); - ib.siteSpecies("HW"); - }); - }); - - lb.addSpecies(sb -> { - sb.genus("S", controlMap); - sb.percentGenus(18.1f); - sb.addSp64Distribution("HW", 100); - sb.addSite(ib -> { - ib.siteCurveNumber(37); - ib.siteSpecies("HE"); - }); - }); - - }); - - }); - - app.init(resolver, controlMap); - - var result = app.processPolygon(0, poly).get(); - - assertThat(result, hasProperty("polygonIdentifier", isPolyId("TestPoly", 2024))); - assertThat(result, hasProperty("biogeoclimaticZone", isBec("IDF"))); - assertThat(result, hasProperty("forestInventoryZone", blankString())); - assertThat(result, hasProperty("mode", present(is(PolygonMode.START)))); - assertThat(result, hasProperty("percentAvailable", is(85f))); - - var primaryLayer = assertOnlyPrimaryLayer(result); - - assertThat(primaryLayer, hasProperty("ageTotal", present(closeTo(200)))); - assertThat(primaryLayer, hasProperty("breastHeightAge", present(closeTo(190.3f)))); - assertThat(primaryLayer, hasProperty("yearsToBreastHeight", present(closeTo(9.7f)))); - - assertThat(primaryLayer, hasProperty("primaryGenus", present(is("H")))); - - assertThat(primaryLayer, hasProperty("height", present(closeTo(32f)))); - assertThat(primaryLayer, hasProperty("inventoryTypeGroup", present(is(14)))); - assertThat(primaryLayer, hasProperty("empiricalRelationshipParameterIndex", present(is(76)))); - - VdypSpecies resultSpecB = TestUtils.assertHasSpecies(primaryLayer, "B", "C", "H", "S"); - - assertThat(resultSpecB, hasProperty("loreyHeightByUtilization", utilizationHeight(6.9609f, 28.8938f))); - assertThat( - resultSpecB, hasProperty( - "baseAreaByUtilization", - utilization( - 1.3540176e-05f, 2.32941175f, 0.0410699844f, 0.0533111095f, 0.156856298f, 2.07817435f - ) - ) - ); - assertThat( - resultSpecB, - hasProperty( - "quadraticMeanDiameterByUtilization", utilization(6.0f, 27.9f, 9.5f, 14.3f, 18.9f, 33.7f) - ) - ); - assertThat( - resultSpecB, hasProperty( - "treesPerHectareByUtilization", utilization(0.00f, 32.32f, 4.94f, 2.84f, 4.75f, 19.79f) - ) - ); - - assertThat( - resultSpecB, hasProperty( - "wholeStemVolumeByUtilization", utilization( - 0.0f, 23.7363f, 0.1417f, 0.2774f, 1.1223f, 22.1949f - ) - ) - ); - assertThat( - resultSpecB, - hasProperty( - "closeUtilizationVolumeByUtilization", utilization( - 0f, 47.5739288f, 0.0133f, 0.2142f, 0.9958f, 21.1897f - ) - ) - ); - assertThat( - resultSpecB, - hasProperty( - "closeUtilizationVolumeNetOfDecayByUtilization", utilization( - 0f, 19.9208f, 0.0121f, 0.1959f, 0.9049f, 18.8082f - ) - ) - ); - assertThat( - resultSpecB, - hasProperty( - "closeUtilizationVolumeNetOfDecayAndWasteByUtilization", - utilization(0f, 19.3051f, 0.0119f, 0.1910f, 0.8818f, 18.2204f) - ) - ); - assertThat( - resultSpecB, - hasProperty( - "closeUtilizationVolumeNetOfDecayWasteAndBreakageByUtilization", - utilization(0f, 18.8506f, 0.0116f, 0.1867f, 0.8619f, 17.7904f) - ) - ); - - assertThat( - primaryLayer, hasProperty("loreyHeightByUtilization", utilizationHeight(7.1220f, 28.8768f)) - ); - assertThat( - primaryLayer, - hasProperty( - "baseAreaByUtilization", - utilization(0.00264f, 66.00000f, 0.78242f, 2.27256f, 3.75915f, 59.18587f) - ) - ); - assertThat( - primaryLayer, - hasProperty( - "quadraticMeanDiameterByUtilization", - utilization(6.1f, 31.4f, 9.1f, 14.1f, 18.8f, 41.0f) - ) - ); - assertThat( - primaryLayer, - hasProperty( - "treesPerHectareByUtilization", - utilization(0.90f, 850.00f, 120.67f, 144.77f, 135.80f, 448.77f) - ) - ); - - assertThat( - primaryLayer, - hasProperty( - "closeUtilizationVolumeNetOfDecayWasteAndBreakageByUtilization", - utilization(0, 252.98407f, 0.0354338735f, 4.66429567f, 14.5271645f, 233.757172f) - ) - ); - - app.close(); - } - } void mockInputStreamFactory(