From 8050fd1659ccbe9488bb098add563340b18f16fe Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Thu, 26 Sep 2024 13:38:07 -0700 Subject: [PATCH 01/22] Add Vdyp Back, create VdypBackApplication stub and VdypProcessingApplication to share code with VDYP Forward --- lib/pom.xml | 1 + lib/vdyp-back/pom.xml | 109 ++++++++ .../nrs/vdyp/back/VdypBackApplication.java | 40 +++ .../src/main/resources/application.properties | 24 ++ .../src/main/resources/logging.properties | 3 + .../ca/bc/gov/nrs/vdyp/application/Pass.java | 20 ++ .../gov/nrs/vdyp/application/Processor.java | 103 ++++++++ .../VdypProcessingApplication.java | 122 +++++++++ .../VdypProcessingApplicationTest.java | 236 ++++++++++++++++++ .../bc/gov/nrs/vdyp/vri/model/VriLayer.java | 1 + 10 files changed, 659 insertions(+) create mode 100644 lib/vdyp-back/pom.xml create mode 100644 lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/VdypBackApplication.java create mode 100644 lib/vdyp-back/src/main/resources/application.properties create mode 100644 lib/vdyp-back/src/main/resources/logging.properties create mode 100644 lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Pass.java create mode 100644 lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java create mode 100644 lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java create mode 100644 lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java diff --git a/lib/pom.xml b/lib/pom.xml index 63566ca19..f1de649ab 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -34,6 +34,7 @@ vdyp-sindex vdyp-si32 vdyp-forward + vdyp-back diff --git a/lib/vdyp-back/pom.xml b/lib/vdyp-back/pom.xml new file mode 100644 index 000000000..73bd8d8be --- /dev/null +++ b/lib/vdyp-back/pom.xml @@ -0,0 +1,109 @@ + + 4.0.0 + + vdyp-back + jar + + Variable Density Yield Project - Back + http://maven.apache.org + + + ca.bc.gov.nrs.vdyp + vdyp-lib + 0.0.1-SNAPSHOT + + + + + ca.bc.gov.nrs.vdyp + vdyp-common + 0.0.1-SNAPSHOT + + + ca.bc.gov.nrs.vdyp + vdyp-forward + 0.0.1-SNAPSHOT + + + + ca.bc.gov.nrs.vdyp + vdyp-common + ${project.version} + test-jar + test + + + + org.apache.commons + commons-lang3 + + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-jdk14 + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.hamcrest + hamcrest + test + + + + org.easymock + easymock + test + + + + ca.bc.gov.nrs.vdyp + vdyp-si32 + 0.0.1-SNAPSHOT + + + + + + src/main/resources/application.properties + + + + src/main/resources + true + + + + + src/test/resources + true + + + + + + net.revelc.code.formatter + formatter-maven-plugin + + + ca.bc.gov.nrs.vdyp + vdyp-buildtools + ${project.version} + + + + + + + + diff --git a/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/VdypBackApplication.java b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/VdypBackApplication.java new file mode 100644 index 000000000..db74d8d91 --- /dev/null +++ b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/VdypBackApplication.java @@ -0,0 +1,40 @@ +package ca.bc.gov.nrs.vdyp.back; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ca.bc.gov.nrs.vdyp.application.Processor; +import ca.bc.gov.nrs.vdyp.application.VdypApplicationIdentifier; +import ca.bc.gov.nrs.vdyp.application.VdypProcessingApplication; + +public class VdypBackApplication extends VdypProcessingApplication { + + static { + initLogging(VdypBackApplication.class); + } + + static final Logger logger = LoggerFactory.getLogger(VdypBackApplication.class); + + public static final String DEFAULT_VDYP_CONTROL_FILE_NAME = "vdyp.ctr"; + + public static void main(final String... args) { + + var app = new VdypBackApplication(); + System.exit(app.run(args)); + } + + @Override + protected Processor getProcessor() { + return null; // TODO + } + + @Override + public String getDefaultControlFileName() { + return DEFAULT_VDYP_CONTROL_FILE_NAME; + } + + @Override + public VdypApplicationIdentifier getId() { + return VdypApplicationIdentifier.VDYP_BACK; + } +} diff --git a/lib/vdyp-back/src/main/resources/application.properties b/lib/vdyp-back/src/main/resources/application.properties new file mode 100644 index 000000000..3fc076a15 --- /dev/null +++ b/lib/vdyp-back/src/main/resources/application.properties @@ -0,0 +1,24 @@ +COMPANY_NAME=Vivid Solutions, Inc. +BINARY_PRODUCT=VDYP7 +BINARY_EXTENSION=JAR +VERSION_MAJOR=8 +VERSION_MINOR=0 +VERSION_INC=0 +VERSION_BUILD=${env.BUILD_NUMBER} +VERSION_YEAR=2023 +VERSION_MONTH=12 +VERSION_DAY=11 +COPYRIGHT_START=2023 +COPYRIGHT_END=2023 +VERSION_CONTROL_SYSTEM=git +VERSION_CONTROL_VERSION=2023 +BRANCH_NAME=main +LAST_COMMIT_REFERENCE= +LAST_COMMIT_AUTHOR= +LAST_COMMIT_DATE= +BUILD_MACHINE=${env.NODE_NAME} +ENV_COMPILER=javac +ENV_COMPILER_VER=17 +ENV_BUILD_CONFIG=Release +ENV_OS=JVM +ENV_ARCH=jdk-17.0.2.jdk \ No newline at end of file diff --git a/lib/vdyp-back/src/main/resources/logging.properties b/lib/vdyp-back/src/main/resources/logging.properties new file mode 100644 index 000000000..759315742 --- /dev/null +++ b/lib/vdyp-back/src/main/resources/logging.properties @@ -0,0 +1,3 @@ +handlers = java.util.logging.ConsoleHandler +java.util.logging.ConsoleHandler.level = FINE +.level = FINE diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Pass.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Pass.java new file mode 100644 index 000000000..14c74412a --- /dev/null +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Pass.java @@ -0,0 +1,20 @@ +package ca.bc.gov.nrs.vdyp.application; + +public enum Pass { + INITIALIZE("Perform Initiation activities"), + OPEN_FILES("Open the stand data files"), + PROCESS_STANDS("Process stands"), + MULTIPLE_STANDS("Allow multiple polygons"), + CLOSE_FILES("Close data files"), + ADDITIONAL_BASE_AREA_CRITERIA("Impose additional base area criteria"); + + final String description; + + private Pass(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } +} diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java new file mode 100644 index 000000000..1f85437f2 --- /dev/null +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java @@ -0,0 +1,103 @@ +package ca.bc.gov.nrs.vdyp.application; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ca.bc.gov.nrs.vdyp.io.FileResolver; +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; + +/** + * + * The overall algorithm of a VDYP Application. + * + */ +public abstract class Processor { + + private static final Logger logger = LoggerFactory.getLogger(Processor.class); + + /** + * Initialize Processor + * + * @param inputFileResolver + * @param outputFileResolver + * @param controlFileNames + * + * @throws IOException + * @throws ResourceParseException + * @throws ProcessingException + */ + public void run( + FileResolver inputFileResolver, FileResolver outputFileResolver, List controlFileNames, + Set vdypPassSet + ) throws IOException, ResourceParseException, ProcessingException { + + logPass(vdypPassSet); + + // Load the control map + Map controlMap = new HashMap<>(); + + var parser = getControlFileParser(); + + for (var controlFileName : controlFileNames) { + logger.info("Resolving and parsing {}", controlFileName); + + try (var is = inputFileResolver.resolveForInput(controlFileName)) { + Path controlFilePath = inputFileResolver.toPath(controlFileName).getParent(); + FileSystemFileResolver relativeResolver = new FileSystemFileResolver(controlFilePath); + + parser.parse(is, relativeResolver, controlMap); + } + } + + process(vdypPassSet, controlMap, Optional.of(outputFileResolver)); + } + + /** + * Get a parser for the control file for this application + * + * @return + */ + protected abstract BaseControlParser getControlFileParser(); + + /** + * @return all possible values of the pass enum + */ + protected Set getAllPasses() { + return EnumSet.allOf(Pass.class); + }; + + /** + * Log the settings of the pass set + * + * @param vdypPassSet + */ + protected void logPass(Set active) { + logger.atInfo().addArgument(active).setMessage("VDYPPASS: {}").log(); + for (var pass : getAllPasses()) { + String activeIndicator = active.contains(pass) ? "☑" : "☐"; + logger.atDebug().addArgument(activeIndicator).addArgument(pass.toString()).addArgument(pass.getDescription()); + } + } + + /** + * Implements + * + * @param outputFileResolver + * + * @throws ProcessingException + */ + public abstract void process( + Set vdypPassSet, Map controlMap, Optional outputFileResolver + ) throws ProcessingException; +} diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java new file mode 100644 index 000000000..0d9f48803 --- /dev/null +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java @@ -0,0 +1,122 @@ +package ca.bc.gov.nrs.vdyp.application; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.logging.LogManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ca.bc.gov.nrs.vdyp.io.FileSystemFileResolver; + +public abstract class VdypProcessingApplication extends VdypApplication { + + @SuppressWarnings("java:S106") + protected static void initLogging(Class klazz) { + try { + LogManager.getLogManager().readConfiguration( + klazz.getClassLoader().getResourceAsStream("logging.properties") + ); + } catch (SecurityException | IOException e) { + System.err.println("Unable to configure logging system"); + } + } + + static final Logger logger = LoggerFactory.getLogger(VdypProcessingApplication.class); + + public abstract String getDefaultControlFileName(); + + protected abstract Processor getProcessor(); + + public static final int CONFIG_LOAD_ERROR_EXIT = 1; + public static final int PROCESSING_ERROR_EXIT = 2; + public static final int NO_ERROR_EXIT = 0; + + protected Set getAllPasses() { + return EnumSet.allOf(Pass.class); + } + + protected VdypProcessingApplication() { + super(); + } + + @SuppressWarnings("java:S106") // Using System.out for direct, console based user interaction. + public int run(final String... args) { + return run(System.out, System.in, args); + } + + public int run(final PrintStream os, final InputStream is, final String... args) { + logVersionInformation(); + + final List controlFileNames; + + try { + if (args.length == 0) { + controlFileNames = getControlFileNamesFromUser(os, is); + } else { + controlFileNames = Arrays.asList(args); + } + } catch (Exception ex) { + logger.error("Error during initialization", ex); + return CONFIG_LOAD_ERROR_EXIT; + } + + try { + Processor processor = getProcessor(); + + processor.run(new FileSystemFileResolver(), new FileSystemFileResolver(), controlFileNames, getAllPasses()); + + System.err.println("Blah"); + + } catch (Exception ex) { + logger.error("Error during processing", ex); + return PROCESSING_ERROR_EXIT; + } + + return NO_ERROR_EXIT; + + } + + public List getControlFileNamesFromUser(final PrintStream os, final InputStream is) throws IOException { + final String defaultFilename = getDefaultControlFileName(); + List controlFileNames; + os.print( + MessageFormat.format( + "Enter name of control file (or RETURN for {1}) or *name for both): ", + defaultFilename + ) + ); + + controlFileNames = new ArrayList<>(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) { + String userResponse = br.readLine(); + if (userResponse.length() == 0) { + controlFileNames.add(defaultFilename); + } else { + if (userResponse.startsWith("*")) { + controlFileNames.add(defaultFilename); + userResponse = userResponse.substring(1); + } + controlFileNames.addAll(Arrays.asList(userResponse.split("\s+"))); + } + + } + return controlFileNames; + } + + private void logVersionInformation() { + logger.info("{} {}", RESOURCE_SHORT_VERSION, RESOURCE_VERSION_DATE); + logger.info("{} Ver:{} {}", RESOURCE_BINARY_NAME, RESOURCE_SHORT_VERSION, RESOURCE_VERSION_DATE); + logger.info("VDYP7 Support Ver: {}", AVERSION); + } + +} \ No newline at end of file diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java new file mode 100644 index 000000000..a122d8e52 --- /dev/null +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java @@ -0,0 +1,236 @@ +package ca.bc.gov.nrs.vdyp.application; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.EnumSet; +import java.util.List; + +import org.easymock.EasyMock; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import ca.bc.gov.nrs.vdyp.io.FileResolver; + +class VdypProcessingApplicationTest { + + @Nested + class Run { + + VdypProcessingApplication app; + Processor processor; + + @BeforeEach + void init() { + processor = EasyMock.createMock(Processor.class); + + app = new VdypProcessingApplication() { + + @Override + public String getDefaultControlFileName() { + return "default.ctl"; + } + + @Override + protected Processor getProcessor() { + return processor; + } + + @Override + public VdypApplicationIdentifier getId() { + fail(); + return null; + } + }; + + } + + @Test + void testCommandLineControlNoError() throws Exception { + var outBytes = new ByteArrayOutputStream(); + var outPrint = new PrintStream(outBytes); + var input = new ByteArrayInputStream("\n".getBytes()); + + processor.run( + EasyMock.isA(FileResolver.class), + EasyMock.isA(FileResolver.class), + EasyMock.eq(List.of("argument.ctl")), + EasyMock.eq(EnumSet.allOf(Pass.class)) + ); + EasyMock.expectLastCall().once(); + + EasyMock.replay(processor); + + int result = app.run(outPrint, input, "argument.ctl"); + + assertThat(result, is(0)); + + EasyMock.verify(processor); + } + + @Test + void testMultipleCommandLineControlNoError() throws Exception { + var outBytes = new ByteArrayOutputStream(); + var outPrint = new PrintStream(outBytes); + var input = new ByteArrayInputStream("\n".getBytes()); + + processor.run( + EasyMock.isA(FileResolver.class), + EasyMock.isA(FileResolver.class), + EasyMock.eq(List.of("argument1.ctl", "argument2.ctl")), + EasyMock.eq(EnumSet.allOf(Pass.class)) + ); + EasyMock.expectLastCall().once(); + + EasyMock.replay(processor); + + int result = app.run(outPrint, input, "argument1.ctl", "argument2.ctl"); + + assertThat(result, is(0)); + + EasyMock.verify(processor); + } + + @Test + void testConsoleInputControlNoError() throws Exception { + var outBytes = new ByteArrayOutputStream(); + var outPrint = new PrintStream(outBytes); + var input = new ByteArrayInputStream("alternate1.ctl alternate2.ctl\n".getBytes()); + + processor.run( + EasyMock.isA(FileResolver.class), + EasyMock.isA(FileResolver.class), + EasyMock.eq(List.of("alternate1.ctl", "alternate2.ctl")), + EasyMock.eq(EnumSet.allOf(Pass.class)) + ); + EasyMock.expectLastCall().once(); + + EasyMock.replay(processor); + + int result = app.run(outPrint, input); + + assertThat(result, is(0)); + + EasyMock.verify(processor); + } + + @Test + void testErrorGettingControlFileNames() throws Exception { + PrintStream outPrint = null; + InputStream input = null; + + EasyMock.replay(processor); + + int result = app.run(outPrint, input); + + assertThat(result, is(1)); + + EasyMock.verify(processor); + } + + @Test + void testErrorWhileProcessing() throws Exception { + var outBytes = new ByteArrayOutputStream(); + var outPrint = new PrintStream(outBytes); + var input = new ByteArrayInputStream("\n".getBytes()); + + processor.run( + EasyMock.isA(FileResolver.class), + EasyMock.isA(FileResolver.class), + EasyMock.eq(List.of("argument.ctl")), + EasyMock.eq(EnumSet.allOf(Pass.class)) + ); + EasyMock.expectLastCall().andThrow(new ProcessingException("Test")).once(); + + EasyMock.replay(processor); + + int result = app.run(outPrint, input, "argument.ctl"); + + assertThat(result, is(2)); + + EasyMock.verify(processor); + } + } + + @Nested + class GetControlFileNamesFromUser { + + VdypProcessingApplication app; + + @BeforeEach + void init() { + app = new VdypProcessingApplication() { + + @Override + public String getDefaultControlFileName() { + return "default.ctl"; + } + + @Override + protected Processor getProcessor() { + fail(); + return null; + } + + @Override + public VdypApplicationIdentifier getId() { + fail(); + return null; + } + + }; + } + + @Test + void testJustDefault() throws Exception { + var outBytes = new ByteArrayOutputStream(); + var outPrint = new PrintStream(outBytes); + var input = new ByteArrayInputStream("\n".getBytes()); + var result = app.getControlFileNamesFromUser(outPrint, input); + assertThat(result, Matchers.contains("default.ctl")); + } + + @Test + void testOneEntry() throws Exception { + var outBytes = new ByteArrayOutputStream(); + var outPrint = new PrintStream(outBytes); + var input = new ByteArrayInputStream("alternate.ctl\n".getBytes()); + var result = app.getControlFileNamesFromUser(outPrint, input); + assertThat(result, Matchers.contains("alternate.ctl")); + } + + @Test + void testTwoEntries() throws Exception { + var outBytes = new ByteArrayOutputStream(); + var outPrint = new PrintStream(outBytes); + var input = new ByteArrayInputStream("alternate1.ctl alternate2.ctl\n".getBytes()); + var result = app.getControlFileNamesFromUser(outPrint, input); + assertThat(result, Matchers.contains("alternate1.ctl", "alternate2.ctl")); + } + + @Test + void testOneEntryPlusDefault() throws Exception { + var outBytes = new ByteArrayOutputStream(); + var outPrint = new PrintStream(outBytes); + var input = new ByteArrayInputStream("*alternate.ctl\n".getBytes()); + var result = app.getControlFileNamesFromUser(outPrint, input); + assertThat(result, Matchers.contains("default.ctl", "alternate.ctl")); + } + + @Test + void testTwoEntriesPlusDefault() throws Exception { + var outBytes = new ByteArrayOutputStream(); + var outPrint = new PrintStream(outBytes); + var input = new ByteArrayInputStream("*alternate1.ctl alternate2.ctl\n".getBytes()); + var result = app.getControlFileNamesFromUser(outPrint, input); + assertThat(result, Matchers.contains("default.ctl", "alternate1.ctl", "alternate2.ctl")); + } + } +} 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 8c5a5e830..6c35cde2d 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,6 +213,7 @@ protected void check(Collection errors) { @Override protected VriLayer doBuild() { + // We want the reciprocal of the forest fraction. When we output at the other end the derived values are multiplied by the forest fraction canceling this out. float multiplier = 100f / percentAvailable.orElse(100f); VriLayer result = new VriLayer( polygonIdentifier.get(), // From a4ae14a616ee3e75247dc1542ad91d8130039f6a Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 4 Oct 2024 11:14:19 -0700 Subject: [PATCH 02/22] Adapting forward code for back --- .../java/ca/bc/gov/nrs/vdyp/back/VdypBackApplication.java | 5 ++--- .../main/java/ca/bc/gov/nrs/vdyp/application/Processor.java | 2 +- .../gov/nrs/vdyp/application/VdypProcessingApplication.java | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/VdypBackApplication.java b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/VdypBackApplication.java index db74d8d91..f249cc42f 100644 --- a/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/VdypBackApplication.java +++ b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/VdypBackApplication.java @@ -3,11 +3,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ca.bc.gov.nrs.vdyp.application.Processor; import ca.bc.gov.nrs.vdyp.application.VdypApplicationIdentifier; import ca.bc.gov.nrs.vdyp.application.VdypProcessingApplication; -public class VdypBackApplication extends VdypProcessingApplication { +public class VdypBackApplication extends VdypProcessingApplication { static { initLogging(VdypBackApplication.class); @@ -24,7 +23,7 @@ public static void main(final String... args) { } @Override - protected Processor getProcessor() { + protected BackProcessor getProcessor() { return null; // TODO } diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java index 1f85437f2..569413ca4 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java @@ -75,7 +75,7 @@ public void run( */ protected Set getAllPasses() { return EnumSet.allOf(Pass.class); - }; + } /** * Log the settings of the pass set diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java index 0d9f48803..2a5ec2b15 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java @@ -18,7 +18,7 @@ import ca.bc.gov.nrs.vdyp.io.FileSystemFileResolver; -public abstract class VdypProcessingApplication extends VdypApplication { +public abstract class VdypProcessingApplication

extends VdypApplication { @SuppressWarnings("java:S106") protected static void initLogging(Class klazz) { @@ -35,7 +35,7 @@ protected static void initLogging(Class klazz) { public abstract String getDefaultControlFileName(); - protected abstract Processor getProcessor(); + protected abstract P getProcessor(); public static final int CONFIG_LOAD_ERROR_EXIT = 1; public static final int PROCESSING_ERROR_EXIT = 2; From 0e6bf39fa913066f6cc675de00a33381a77e380e Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 4 Oct 2024 13:08:08 -0700 Subject: [PATCH 03/22] Add python cach directory to git ignore list --- tools/src/python/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 tools/src/python/.gitignore diff --git a/tools/src/python/.gitignore b/tools/src/python/.gitignore new file mode 100644 index 000000000..bee8a64b7 --- /dev/null +++ b/tools/src/python/.gitignore @@ -0,0 +1 @@ +__pycache__ From dc35b80f6866abee8c78145f7d8fb7c1410f1912 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 4 Oct 2024 13:06:25 -0700 Subject: [PATCH 04/22] Back Prep --- lib/vdyp-back/pom.xml | 9 +- .../nrs/vdyp/back/BackProcessingEngine.java | 24 + .../bc/gov/nrs/vdyp/back/BackProcessor.java | 29 ++ .../BackLayerProcessingState.java | 43 ++ .../processing_state/BackProcessingState.java | 110 +++++ .../vdyp/application/ProcessingEngine.java | 5 + .../gov/nrs/vdyp/processing_state/Bank.java | 465 ++++++++++++++++++ .../LayerProcessingState.java | 157 ++++++ .../processing_state/ProcessingState.java | 111 +++++ .../VdypProcessingApplicationTest.java | 10 +- lib/vdyp-forward/pom.xml | 34 +- 11 files changed, 980 insertions(+), 17 deletions(-) create mode 100644 lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngine.java create mode 100644 lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessor.java create mode 100644 lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackLayerProcessingState.java create mode 100644 lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackProcessingState.java create mode 100644 lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/ProcessingEngine.java create mode 100644 lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/Bank.java create mode 100644 lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/LayerProcessingState.java create mode 100644 lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/ProcessingState.java diff --git a/lib/vdyp-back/pom.xml b/lib/vdyp-back/pom.xml index 73bd8d8be..49b081646 100644 --- a/lib/vdyp-back/pom.xml +++ b/lib/vdyp-back/pom.xml @@ -25,7 +25,7 @@ vdyp-forward 0.0.1-SNAPSHOT - + ca.bc.gov.nrs.vdyp vdyp-common @@ -33,6 +33,13 @@ test-jar test + + ca.bc.gov.nrs.vdyp + vdyp-forward + ${project.version} + test-jar + test + org.apache.commons diff --git a/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngine.java b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngine.java new file mode 100644 index 000000000..8f1ce44a1 --- /dev/null +++ b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngine.java @@ -0,0 +1,24 @@ +package ca.bc.gov.nrs.vdyp.back; + +import ca.bc.gov.nrs.vdyp.application.ProcessingEngine; +import ca.bc.gov.nrs.vdyp.application.ProcessingException; +import ca.bc.gov.nrs.vdyp.application.StandProcessingException; +import ca.bc.gov.nrs.vdyp.back.processing_state.BackProcessingState; + +public class BackProcessingEngine extends ProcessingEngine { + + /** + * + * @throws StandProcessingException + */ + // BACKPREP + void prepare(BackProcessingState state) throws ProcessingException { + + state.setBaseAreaVeteran( + state.getVeteranLayerProcessingState() + .map(vetState -> vetState.getBank().basalAreas[0][0 + 1]) + ); + + + } +} diff --git a/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessor.java b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessor.java new file mode 100644 index 000000000..f97ffabb6 --- /dev/null +++ b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessor.java @@ -0,0 +1,29 @@ +package ca.bc.gov.nrs.vdyp.back; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import ca.bc.gov.nrs.vdyp.application.Pass; +import ca.bc.gov.nrs.vdyp.application.ProcessingException; +import ca.bc.gov.nrs.vdyp.application.Processor; +import ca.bc.gov.nrs.vdyp.io.FileResolver; +import ca.bc.gov.nrs.vdyp.io.parse.control.BaseControlParser; + +public class BackProcessor extends Processor { + + @Override + protected BaseControlParser getControlFileParser() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void process( + Set vdypPassSet, Map controlMap, Optional outputFileResolver + ) throws ProcessingException { + // TODO Auto-generated method stub + + } + +} diff --git a/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackLayerProcessingState.java b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackLayerProcessingState.java new file mode 100644 index 000000000..de5b52a22 --- /dev/null +++ b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackLayerProcessingState.java @@ -0,0 +1,43 @@ +package ca.bc.gov.nrs.vdyp.back.processing_state; + +import java.util.function.Predicate; + +import ca.bc.gov.nrs.vdyp.application.ProcessingException; +import ca.bc.gov.nrs.vdyp.forward.controlmap.ForwardResolvedControlMap; +import ca.bc.gov.nrs.vdyp.model.LayerType; +import ca.bc.gov.nrs.vdyp.model.VdypLayer; +import ca.bc.gov.nrs.vdyp.model.VdypPolygon; +import ca.bc.gov.nrs.vdyp.model.VdypSpecies; +import ca.bc.gov.nrs.vdyp.processing_state.LayerProcessingState; +import ca.bc.gov.nrs.vdyp.processing_state.ProcessingState; + +public class BackLayerProcessingState extends + LayerProcessingState { + + protected BackLayerProcessingState( + ProcessingState ps, VdypPolygon polygon, + LayerType subjectLayerType + ) throws ProcessingException { + super(ps, polygon, subjectLayerType); + // TODO Auto-generated constructor stub + } + + @Override + protected Predicate getBankFilter() { + // TODO Auto-generated method stub + return x -> true; + } + + @Override + protected void applyCompatibilityVariables(VdypSpecies species, int i) { + // TODO Auto-generated method stub + + } + + @Override + protected VdypLayer updateLayerFromBank() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackProcessingState.java b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackProcessingState.java new file mode 100644 index 000000000..fc1cd4523 --- /dev/null +++ b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackProcessingState.java @@ -0,0 +1,110 @@ +package ca.bc.gov.nrs.vdyp.back.processing_state; + +import java.util.Map; +import java.util.Optional; + +import ca.bc.gov.nrs.vdyp.application.ProcessingException; +import ca.bc.gov.nrs.vdyp.forward.controlmap.ForwardResolvedControlMap; +import ca.bc.gov.nrs.vdyp.model.MatrixMap2; +import ca.bc.gov.nrs.vdyp.model.UtilizationClass; +import ca.bc.gov.nrs.vdyp.model.UtilizationClassVariable; +import ca.bc.gov.nrs.vdyp.model.VdypLayer; +import ca.bc.gov.nrs.vdyp.model.VdypPolygon; +import ca.bc.gov.nrs.vdyp.model.VolumeVariable; +import ca.bc.gov.nrs.vdyp.processing_state.ProcessingState; + +public class BackProcessingState extends ProcessingState { + + Optional baseAreaVeteran = Optional.empty(); // BACK1/BAV + + private static final String COMPATIBILITY_VARIABLES_SET_CAN_BE_SET_ONCE_ONLY = "CompatibilityVariablesSet can be set once only"; + private static final String UNSET_CV_VOLUMES = "unset cvVolumes"; + private static final String UNSET_CV_BASAL_AREAS = "unset cvBasalAreas"; + + // Compatibility Variables - LCV1 & LCVS + private boolean areCompatibilityVariablesSet = false; + + private MatrixMap2[] cvVolume; + private Map[] cvBasalArea; + private Map[] cvQuadraticMeanDiameter; + private Map[] cvPrimaryLayerSmall; + + public BackProcessingState(Map controlMap) throws ProcessingException { + super(controlMap); + // TODO Auto-generated constructor stub + } + + @Override + public ForwardResolvedControlMap resolveControlMap(Map controlMap) { + // TODO Auto-generated method stub + return null; + } + + @Override + protected BackLayerProcessingState createLayerState(VdypPolygon polygon, VdypLayer layer) + throws ProcessingException { + return new BackLayerProcessingState(this, polygon, layer.getLayerType()); + } + + public void setBaseAreaVeteran(Optional baseAreaVeteran) { + this.baseAreaVeteran = baseAreaVeteran; + } + + public void setBaseAreaVeteran(float baseAreaVeteran) { + this.baseAreaVeteran = Optional.of(baseAreaVeteran); + } + + public Optional getBaseAreaVeteran() { + return baseAreaVeteran; + } + + public void setCompatibilityVariableDetails( + MatrixMap2[] cvVolume, + Map[] cvBasalArea, + Map[] cvQuadraticMeanDiameter, + Map[] cvPrimaryLayerSmall + ) { + if (areCompatibilityVariablesSet) { + throw new IllegalStateException(COMPATIBILITY_VARIABLES_SET_CAN_BE_SET_ONCE_ONLY); + } + + this.cvVolume = cvVolume; + this.cvBasalArea = cvBasalArea; + this.cvQuadraticMeanDiameter = cvQuadraticMeanDiameter; + this.cvPrimaryLayerSmall = cvPrimaryLayerSmall; + + areCompatibilityVariablesSet = true; + } + + public float getCVVolume(int speciesIndex, UtilizationClass uc, VolumeVariable volumeVariable) { + if (!areCompatibilityVariablesSet) { + throw new IllegalStateException(UNSET_CV_VOLUMES); + } + + return cvVolume[speciesIndex].get(uc, volumeVariable); + } + + public float getCVBasalArea(int speciesIndex, UtilizationClass uc) { + if (!areCompatibilityVariablesSet) { + throw new IllegalStateException(UNSET_CV_BASAL_AREAS); + } + + return cvBasalArea[speciesIndex].get(uc); + } + + public float getCVQuadraticMeanDiameter(int speciesIndex, UtilizationClass uc) { + if (!areCompatibilityVariablesSet) { + throw new IllegalStateException(UNSET_CV_BASAL_AREAS); + } + + return cvQuadraticMeanDiameter[speciesIndex].get(uc); + } + + public float getCVSmall(int speciesIndex, UtilizationClassVariable variable) { + if (!areCompatibilityVariablesSet) { + throw new IllegalStateException(UNSET_CV_BASAL_AREAS); + } + + return cvPrimaryLayerSmall[speciesIndex].get(variable); + } +} diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/ProcessingEngine.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/ProcessingEngine.java new file mode 100644 index 000000000..225e00074 --- /dev/null +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/ProcessingEngine.java @@ -0,0 +1,5 @@ +package ca.bc.gov.nrs.vdyp.application; + +public class ProcessingEngine { + +} diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/Bank.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/Bank.java new file mode 100644 index 000000000..364abf310 --- /dev/null +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/Bank.java @@ -0,0 +1,465 @@ +package ca.bc.gov.nrs.vdyp.processing_state; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.IntStream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ca.bc.gov.nrs.vdyp.application.ProcessingException; +import ca.bc.gov.nrs.vdyp.common.Utils; +import ca.bc.gov.nrs.vdyp.common_calculators.BaseAreaTreeDensityDiameter; +import ca.bc.gov.nrs.vdyp.model.BecDefinition; +import ca.bc.gov.nrs.vdyp.model.Sp64DistributionSet; +import ca.bc.gov.nrs.vdyp.model.UtilizationClass; +import ca.bc.gov.nrs.vdyp.model.VdypEntity; +import ca.bc.gov.nrs.vdyp.model.VdypLayer; +import ca.bc.gov.nrs.vdyp.model.VdypSite; +import ca.bc.gov.nrs.vdyp.model.VdypSpecies; +import ca.bc.gov.nrs.vdyp.model.VdypUtilizationHolder; + +public class Bank { + + @SuppressWarnings("unused") + private static final Logger logger = LoggerFactory.getLogger(Bank.class); + + private static final int N_UTILIZATION_CLASSES = UtilizationClass.values().length; + + private final VdypLayer layer; + private final BecDefinition becZone; + + /** + * The number of species in the state. Note that all arrays have this value plus one elements in them; the element + * at index 0 is unused for the species values* and contains the default utilization in the Utilization values. + * + * (*) except: siteCurveNumbers[0] is used to store the site curve of the primary species. + */ + private int nSpecies; // BANK1 NSPB + private int[] indices; + + // Species information + + public final String[/* nSpecies + 1 */] speciesNames; // BANK2 SP0B + public final Sp64DistributionSet[/* nSpecies + 1 */] sp64Distributions; // BANK2 SP64DISTB + public final float[/* nSpecies + 1 */] siteIndices; // BANK3 SIB + public final float[/* nSpecies + 1 */] dominantHeights; // BANK3 HDB + public final float[/* nSpecies + 1 */] ageTotals; // BANK3 AGETOTB + public final float[/* nSpecies + 1 */] yearsAtBreastHeight; // BANK3 AGEBHB + public final float[/* nSpecies + 1 */] yearsToBreastHeight; // BANK3 YTBHB + public final int[/* nSpecies + 1 */] siteCurveNumbers; // BANK3 SCNB + public final int[/* nSpecies + 1 */] speciesIndices; // BANK1 ISPB + public final float[/* nSpecies + 1 */] percentagesOfForestedLand; // BANK1 PCTB + + // Utilization information, per Species + + public final float[/* nSpecies + 1, including 0 */][/* all ucs */] basalAreas; // BANK1 BAB. Units: m^2/hectare + public final float[/* nSpecies + 1, including 0 */][/* all ucs */] closeUtilizationVolumes; // BANK1 VOLCUB + public final float[/* nSpecies + 1, including 0 */][/* all ucs */] cuVolumesMinusDecay; // BANK1 VOL_DB + public final float[/* nSpecies + 1, including 0 */][/* all ucs */] cuVolumesMinusDecayAndWastage; // BANK1 VOL_DW_B + public final float[/* nSpecies + 1, including 0 */][/* uc -1 and 0 only */] loreyHeights; // BANK1 HLB + public final float[/* nSpecies + 1, including 0 */][/* all ucs */] quadMeanDiameters; // BANK1 DQB + public final float[/* nSpecies + 1, including 0 */][/* all ucs */] treesPerHectare; // BANK1 TPHB + public final float[/* nSpecies + 1, including 0 */][/* all ucs */] wholeStemVolumes; // BANK1 VOLWSB + + public Bank(VdypLayer layer, BecDefinition becZone, Predicate retainCriteria) { + + this.layer = layer; + this.becZone = becZone; + + List speciesToRetain = layer.getSpecies().values().stream().filter(s -> retainCriteria.test(s)) + .sorted((s1, s2) -> s1.getGenusIndex() - s2.getGenusIndex()).toList(); + + this.nSpecies = speciesToRetain.size(); + this.indices = IntStream.range(1, nSpecies + 1).toArray(); + + // In the following, index 0 is unused + speciesNames = new String[nSpecies + 1]; + sp64Distributions = new Sp64DistributionSet[getNSpecies() + 1]; + siteIndices = new float[nSpecies + 1]; + dominantHeights = new float[nSpecies + 1]; + ageTotals = new float[nSpecies + 1]; + yearsAtBreastHeight = new float[nSpecies + 1]; + yearsToBreastHeight = new float[nSpecies + 1]; + siteCurveNumbers = new int[nSpecies + 1]; + speciesIndices = new int[nSpecies + 1]; + percentagesOfForestedLand = new float[nSpecies + 1]; + + // In the following, index 0 is used for the default species utilization + basalAreas = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; + closeUtilizationVolumes = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; + cuVolumesMinusDecay = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; + cuVolumesMinusDecayAndWastage = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; + loreyHeights = new float[nSpecies + 1][2]; + quadMeanDiameters = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; + treesPerHectare = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; + wholeStemVolumes = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; + + int nextSlot = 1; + for (VdypSpecies s : speciesToRetain) { + transferSpeciesIntoBank(nextSlot++, s); + } + + transferUtilizationSetIntoBank(0, layer); + + // BANKCHK1 - calculate UC All values from components (rather than rely on the values + // provided in the input.) + + setCalculateUtilizationClassAllValues(); + } + + public Bank(Bank source) { + + this.becZone = source.becZone; + this.layer = source.layer; + + this.nSpecies = source.nSpecies; + this.indices = copy(source.indices); + this.speciesNames = copy(source.speciesNames); + this.speciesIndices = copy(source.speciesIndices); + + this.siteCurveNumbers = copy(source.siteCurveNumbers); + this.sp64Distributions = copy(source.sp64Distributions); + + this.ageTotals = copy(source.ageTotals); + this.dominantHeights = copy(source.dominantHeights); + this.percentagesOfForestedLand = copy(source.percentagesOfForestedLand); + this.siteIndices = copy(source.siteIndices); + this.yearsAtBreastHeight = copy(source.yearsAtBreastHeight); + this.yearsToBreastHeight = copy(source.yearsToBreastHeight); + + this.basalAreas = copy(source.basalAreas); + this.closeUtilizationVolumes = copy(source.closeUtilizationVolumes); + this.cuVolumesMinusDecay = copy(source.cuVolumesMinusDecay); + this.cuVolumesMinusDecayAndWastage = copy(source.cuVolumesMinusDecayAndWastage); + this.loreyHeights = copy(source.loreyHeights); + this.quadMeanDiameters = copy(source.quadMeanDiameters); + this.treesPerHectare = copy(source.treesPerHectare); + this.wholeStemVolumes = copy(source.wholeStemVolumes); + } + + public int getNSpecies() { + return nSpecies; + } + + int[] getIndices() { + return indices; + } + + BecDefinition getBecZone() { + return becZone; + } + + /** + * Refresh the values in the bank with an updated version (given) of the layer used to create the bank. The + * modifications cannot include any changes to the set of species, although the details of those species may of + * course change. + * + * @param layer a (presumably modified) version of the layer. + * @throws ProcessingException + */ + void refreshBank(VdypLayer layer) throws ProcessingException { + + if (!this.layer.equals(layer)) { + throw new IllegalArgumentException( + MessageFormat.format( + "One cannot refresh a bank from a" + + " layer ({0}) different from the one used to create the bank ({1})", + this.layer, layer + ) + ); + } + + List species = layer.getSpecies().values().stream() + .sorted((s1, s2) -> s1.getGenusIndex() - s2.getGenusIndex()).toList(); + + transferUtilizationSetIntoBank(0, layer); + + int nextSlot = 1; + for (VdypSpecies s : species) { + transferSpeciesIntoBank(nextSlot++, s); + } + } + + private void transferSpeciesIntoBank(int index, VdypSpecies species) { + + speciesNames[index] = species.getGenus(); + sp64Distributions[index] = species.getSp64DistributionSet(); + speciesIndices[index] = species.getGenusIndex(); + + species.getSite().ifPresentOrElse(s -> { + siteIndices[index] = s.getSiteIndex().orElse(VdypEntity.MISSING_FLOAT_VALUE); + dominantHeights[index] = s.getHeight().orElse(VdypEntity.MISSING_FLOAT_VALUE); + ageTotals[index] = s.getAgeTotal().orElse(VdypEntity.MISSING_FLOAT_VALUE); + yearsToBreastHeight[index] = s.getYearsToBreastHeight().orElse(VdypEntity.MISSING_FLOAT_VALUE); + if (ageTotals[index] != VdypEntity.MISSING_FLOAT_VALUE + && yearsToBreastHeight[index] != VdypEntity.MISSING_FLOAT_VALUE) { + yearsAtBreastHeight[index] = ageTotals[index] - yearsToBreastHeight[index]; + } else { + yearsAtBreastHeight[index] = VdypEntity.MISSING_FLOAT_VALUE; + } + siteCurveNumbers[index] = s.getSiteCurveNumber().orElse(VdypEntity.MISSING_INTEGER_VALUE); + // percentForestedLand is output-only and so not assigned here. + }, () -> { + siteIndices[index] = VdypEntity.MISSING_FLOAT_VALUE; + dominantHeights[index] = VdypEntity.MISSING_FLOAT_VALUE; + ageTotals[index] = VdypEntity.MISSING_FLOAT_VALUE; + yearsToBreastHeight[index] = VdypEntity.MISSING_FLOAT_VALUE; + yearsAtBreastHeight[index] = VdypEntity.MISSING_FLOAT_VALUE; + siteCurveNumbers[index] = VdypEntity.MISSING_INTEGER_VALUE; + }); + + transferUtilizationSetIntoBank(index, species); + } + + private void transferUtilizationSetIntoBank(int index, VdypUtilizationHolder uh) { + + for (UtilizationClass uc : UtilizationClass.values()) { + int ucIndex = uc.ordinal(); + basalAreas[index][ucIndex] = uh.getBaseAreaByUtilization().get(uc); + closeUtilizationVolumes[index][ucIndex] = uh.getCloseUtilizationVolumeByUtilization().get(uc); + cuVolumesMinusDecay[index][ucIndex] = uh.getCloseUtilizationVolumeNetOfDecayByUtilization().get(uc); + cuVolumesMinusDecayAndWastage[index][ucIndex] = uh + .getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization().get(uc); + if (ucIndex < 2 /* only uc 0 and 1 have a lorey height */) { + loreyHeights[index][ucIndex] = uh.getLoreyHeightByUtilization().get(uc); + } + quadMeanDiameters[index][ucIndex] = uh.getQuadraticMeanDiameterByUtilization().get(uc); + treesPerHectare[index][ucIndex] = uh.getTreesPerHectareByUtilization().get(uc); + wholeStemVolumes[index][ucIndex] = uh.getWholeStemVolumeByUtilization().get(uc); + } + } + + /** + * For each species, set uc All to the sum of the UC values, UC 7.5 and above only, for the summable values, and + * calculate quad-mean-diameter from these values. + *

+ * For the layer, set uc All values (for summable types) to the sum of those of the individual species and set the + * other uc values to the sum of those of the individual species. Calculate the uc All value for quad-mean-diameter, + * and the uc All and Small value for lorey-height. + */ + private void setCalculateUtilizationClassAllValues() { + + int layerIndex = 0; + int ucAllIndex = UtilizationClass.ALL.ordinal(); + int ucSmallIndex = UtilizationClass.SMALL.ordinal(); + + // Each species + + for (int sp0Index : indices) { + + basalAreas[sp0Index][ucAllIndex] = sumUtilizationClassValues( + basalAreas[sp0Index], UtilizationClass.UTIL_CLASSES + ); + treesPerHectare[sp0Index][ucAllIndex] = sumUtilizationClassValues( + treesPerHectare[sp0Index], UtilizationClass.UTIL_CLASSES + ); + wholeStemVolumes[sp0Index][ucAllIndex] = sumUtilizationClassValues( + wholeStemVolumes[sp0Index], UtilizationClass.UTIL_CLASSES + ); + closeUtilizationVolumes[sp0Index][ucAllIndex] = sumUtilizationClassValues( + closeUtilizationVolumes[sp0Index], UtilizationClass.UTIL_CLASSES + ); + cuVolumesMinusDecay[sp0Index][ucAllIndex] = sumUtilizationClassValues( + cuVolumesMinusDecay[sp0Index], UtilizationClass.UTIL_CLASSES + ); + cuVolumesMinusDecayAndWastage[sp0Index][ucAllIndex] = sumUtilizationClassValues( + cuVolumesMinusDecayAndWastage[sp0Index], UtilizationClass.UTIL_CLASSES + ); + + if (basalAreas[sp0Index][ucAllIndex] > 0.0f) { + quadMeanDiameters[sp0Index][ucAllIndex] = BaseAreaTreeDensityDiameter + .quadMeanDiameter(basalAreas[sp0Index][ucAllIndex], treesPerHectare[sp0Index][ucAllIndex]); + } + } + + // Layer + + basalAreas[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues(basalAreas, UtilizationClass.ALL); + treesPerHectare[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues( + treesPerHectare, UtilizationClass.ALL + ); + wholeStemVolumes[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues( + wholeStemVolumes, UtilizationClass.ALL + ); + closeUtilizationVolumes[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues( + closeUtilizationVolumes, UtilizationClass.ALL + ); + cuVolumesMinusDecay[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues( + cuVolumesMinusDecay, UtilizationClass.ALL + ); + cuVolumesMinusDecayAndWastage[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues( + cuVolumesMinusDecayAndWastage, UtilizationClass.ALL + ); + + // Calculate the layer's uc All values for quad-mean-diameter and lorey height + + float sumLoreyHeightByBasalAreaSmall = 0.0f; + float sumBasalAreaSmall = 0.0f; + float sumLoreyHeightByBasalAreaAll = 0.0f; + + for (int sp0Index : indices) { + sumLoreyHeightByBasalAreaSmall += loreyHeights[sp0Index][ucSmallIndex] * basalAreas[sp0Index][ucSmallIndex]; + sumBasalAreaSmall += basalAreas[sp0Index][ucSmallIndex]; + sumLoreyHeightByBasalAreaAll += loreyHeights[sp0Index][ucAllIndex] * basalAreas[sp0Index][ucAllIndex]; + } + + if (basalAreas[layerIndex][ucAllIndex] > 0.0f) { + quadMeanDiameters[layerIndex][ucAllIndex] = BaseAreaTreeDensityDiameter + .quadMeanDiameter(basalAreas[layerIndex][ucAllIndex], treesPerHectare[layerIndex][ucAllIndex]); + loreyHeights[layerIndex][ucAllIndex] = sumLoreyHeightByBasalAreaAll / basalAreas[layerIndex][ucAllIndex]; + } + + // Calculate the layer's lorey height uc Small value + + if (sumBasalAreaSmall > 0.0f) { + loreyHeights[layerIndex][ucSmallIndex] = sumLoreyHeightByBasalAreaSmall / sumBasalAreaSmall; + } + + // Finally, set the layer's summable UC values (other than All, which was computed above) to + // the sums of those of each of the species. + + for (UtilizationClass uc : UtilizationClass.ALL_CLASSES) { + basalAreas[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues(basalAreas, uc); + treesPerHectare[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues(treesPerHectare, uc); + wholeStemVolumes[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues(wholeStemVolumes, uc); + closeUtilizationVolumes[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues( + closeUtilizationVolumes, uc + ); + cuVolumesMinusDecay[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues(cuVolumesMinusDecay, uc); + cuVolumesMinusDecayAndWastage[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues( + cuVolumesMinusDecayAndWastage, uc + ); + } + } + + private float sumUtilizationClassValues(float[] ucValues, List subjects) { + float sum = 0.0f; + + for (UtilizationClass uc : UtilizationClass.values()) { + if (subjects.contains(uc)) { + sum += ucValues[uc.ordinal()]; + } + } + + return sum; + } + + private float sumSpeciesUtilizationClassValues(float[][] ucValues, UtilizationClass uc) { + float sum = 0.0f; + + for (int sp0Index : this.indices) { + sum += ucValues[sp0Index][uc.ordinal()]; + } + + return sum; + } + + /** + * This method copies the Bank contents out to the VdypLayer instance used to create it and returns that. It is a + * relatively expensive operation and should not be called without due consideration. + * + * @return as described + */ + VdypLayer buildLayerFromBank() { + + transferUtilizationsFromBank(0, layer); + + Collection newSpecies = new ArrayList<>(); + for (int i : indices) { + newSpecies.add(transferSpeciesFromBank(i, layer.getSpecies().get(speciesNames[i]))); + } + layer.setSpecies(newSpecies); + + return layer; + } + + private VdypSpecies transferSpeciesFromBank(int index, VdypSpecies species) { + + VdypSpecies newSpecies = VdypSpecies.build(speciesBuilder -> { + speciesBuilder.copy(species); + speciesBuilder.percentGenus(this.percentagesOfForestedLand[index]); + species.getSite().ifPresentOrElse(site -> speciesBuilder.addSite(VdypSite.build(siteBuilder -> { + siteBuilder.copy(site); + siteBuilder.siteGenus(this.speciesNames[index]); + siteBuilder.ageTotal(Utils.optFloat(ageTotals[index])); + siteBuilder.height(Utils.optFloat(this.dominantHeights[index])); + siteBuilder.siteCurveNumber(Utils.optInt(this.siteCurveNumbers[index])); + siteBuilder.siteIndex(Utils.optFloat(this.siteIndices[index])); + siteBuilder.yearsToBreastHeight(Utils.optFloat(this.yearsToBreastHeight[index])); + })), () -> { + VdypSite site = VdypSite.build(siteBuilder -> { + siteBuilder.polygonIdentifier(species.getPolygonIdentifier()); + siteBuilder.layerType(species.getLayerType()); + siteBuilder.siteGenus(this.speciesNames[index]); + siteBuilder.ageTotal(Utils.optFloat(this.ageTotals[index])); + siteBuilder.height(Utils.optFloat(this.dominantHeights[index])); + siteBuilder.siteCurveNumber(Utils.optInt(this.siteCurveNumbers[index])); + siteBuilder.siteIndex(Utils.optFloat(this.siteIndices[index])); + siteBuilder.yearsToBreastHeight(Utils.optFloat(this.yearsToBreastHeight[index])); + }); + + speciesBuilder.addSite(site); + }); + }); + + transferUtilizationsFromBank(index, newSpecies); + + return newSpecies; + } + + private void transferUtilizationsFromBank(int index, VdypUtilizationHolder uh) { + + for (UtilizationClass uc : UtilizationClass.values()) { + int ucIndex = uc.ordinal(); + uh.getBaseAreaByUtilization().set(uc, basalAreas[index][ucIndex]); + uh.getCloseUtilizationVolumeByUtilization().set(uc, closeUtilizationVolumes[index][ucIndex]); + uh.getCloseUtilizationVolumeNetOfDecayByUtilization().set(uc, cuVolumesMinusDecay[index][ucIndex]); + uh.getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization() + .set(uc, cuVolumesMinusDecayAndWastage[index][ucIndex]); + if (ucIndex < 2 /* only uc 0 and 1 have a lorey height */) { + uh.getLoreyHeightByUtilization().set(uc, loreyHeights[index][ucIndex]); + } + uh.getQuadraticMeanDiameterByUtilization().set(uc, quadMeanDiameters[index][ucIndex]); + uh.getTreesPerHectareByUtilization().set(uc, treesPerHectare[index][ucIndex]); + uh.getWholeStemVolumeByUtilization().set(uc, wholeStemVolumes[index][ucIndex]); + } + } + + public Bank copy() { + return new Bank(this); + } + + private String[] copy(String[] a) { + return Arrays.stream(a).toArray(String[]::new); + } + + private int[] copy(int[] a) { + int[] t = new int[a.length]; + + System.arraycopy(a, 0, t, 0, a.length); + + return t; + } + + private float[] copy(float[] a) { + float[] t = new float[a.length]; + + System.arraycopy(a, 0, t, 0, a.length); + + return t; + } + + private float[][] copy(float[][] a) { + return Arrays.stream(a).map(float[]::clone).toArray(float[][]::new); + } + + private Sp64DistributionSet[] copy(Sp64DistributionSet[] sp64Distributions) { + return Arrays.stream(sp64Distributions).map(s -> s == null ? null : s.copy()) + .toArray(Sp64DistributionSet[]::new); + } +} \ No newline at end of file diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/LayerProcessingState.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/LayerProcessingState.java new file mode 100644 index 000000000..79d64fb4d --- /dev/null +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/LayerProcessingState.java @@ -0,0 +1,157 @@ +package ca.bc.gov.nrs.vdyp.processing_state; + +import java.util.Map; +import java.util.function.Predicate; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ca.bc.gov.nrs.vdyp.application.ProcessingException; +import ca.bc.gov.nrs.vdyp.controlmap.ResolvedControlMap; +import ca.bc.gov.nrs.vdyp.model.BecDefinition; +import ca.bc.gov.nrs.vdyp.model.LayerType; +import ca.bc.gov.nrs.vdyp.model.MatrixMap2; +import ca.bc.gov.nrs.vdyp.model.MatrixMap3; +import ca.bc.gov.nrs.vdyp.model.UtilizationClass; +import ca.bc.gov.nrs.vdyp.model.UtilizationClassVariable; +import ca.bc.gov.nrs.vdyp.model.VdypLayer; +import ca.bc.gov.nrs.vdyp.model.VdypPolygon; +import ca.bc.gov.nrs.vdyp.model.VdypSpecies; +import ca.bc.gov.nrs.vdyp.model.VolumeVariable; + +public abstract class LayerProcessingState> { + + private static final Logger logger = LoggerFactory.getLogger(LayerProcessingState.class); + + private static final String COMPATIBILITY_VARIABLES_SET_CAN_BE_SET_ONCE_ONLY = "CompatibilityVariablesSet can be set once only"; + private static final String UNSET_CV_VOLUMES = "unset cvVolumes"; + private static final String UNSET_CV_BASAL_AREAS = "unset cvBasalAreas"; + + /** The containing ForwardProcessingState */ + private final ProcessingState ps; + + /** The containing polygon of the layer on which the Processor is operating */ + private final VdypPolygon polygon; + + /** The type of Layer being processed */ + private final LayerType layerType; + + // L1COM1, L1COM4 and L1COM5 - these common blocks mirror BANK1, BANK2 and BANK3 and are initialized + // when copied to "active" in ForwardProcessingEngine. + + /** + * State of the layer during processing. + */ + private Bank bank; + + // Compatibility Variables - LCV1 & LCVS + private boolean areCompatibilityVariablesSet = false; + + private MatrixMap3[] cvVolume; + private MatrixMap2[] cvBasalArea; + private MatrixMap2[] cvQuadraticMeanDiameter; + private Map[] cvPrimaryLayerSmall; + + protected LayerProcessingState(ProcessingState ps, VdypPolygon polygon, LayerType subjectLayerType) + throws ProcessingException { + + this.ps = ps; + this.polygon = polygon; + this.layerType = subjectLayerType; + + BecDefinition becZone = polygon.getBiogeoclimaticZone(); + + this.bank = new Bank( + polygon.getLayers().get(subjectLayerType), becZone, + getBankFilter() + ); + + } + + protected abstract Predicate getBankFilter(); + + public VdypPolygon getPolygon() { + return polygon; + } + + public LayerType getLayerType() { + return layerType; + } + + public BecDefinition getBecZone() { + return bank.getBecZone(); + } + + + public static Logger getLogger() { + return logger; + } + + public ProcessingState getParent() { + return ps; + } + + public Bank getBank() { + return bank; + } + + protected abstract void applyCompatibilityVariables(VdypSpecies species, int i); + + public int getNSpecies() { + return bank.getNSpecies(); + } + + protected abstract VdypLayer updateLayerFromBank(); + + public void setCompatibilityVariableDetails( + MatrixMap3[] cvVolume, + MatrixMap2[] cvBasalArea, + MatrixMap2[] cvQuadraticMeanDiameter, + Map[] cvPrimaryLayerSmall + ) { + if (areCompatibilityVariablesSet) { + throw new IllegalStateException(COMPATIBILITY_VARIABLES_SET_CAN_BE_SET_ONCE_ONLY); + } + + this.cvVolume = cvVolume; + this.cvBasalArea = cvBasalArea; + this.cvQuadraticMeanDiameter = cvQuadraticMeanDiameter; + this.cvPrimaryLayerSmall = cvPrimaryLayerSmall; + + areCompatibilityVariablesSet = true; + } + + public float + getCVVolume(int speciesIndex, UtilizationClass uc, VolumeVariable volumeVariable, LayerType layerType) { + if (!areCompatibilityVariablesSet) { + throw new IllegalStateException(UNSET_CV_VOLUMES); + } + + return cvVolume[speciesIndex].get(uc, volumeVariable, layerType); + } + + public float getCVBasalArea(int speciesIndex, UtilizationClass uc, LayerType layerType) { + if (!areCompatibilityVariablesSet) { + throw new IllegalStateException(UNSET_CV_BASAL_AREAS); + } + + return cvBasalArea[speciesIndex].get(uc, layerType); + } + + public float getCVQuadraticMeanDiameter(int speciesIndex, UtilizationClass uc, LayerType layerType) { + if (!areCompatibilityVariablesSet) { + throw new IllegalStateException(UNSET_CV_BASAL_AREAS); + } + + return cvQuadraticMeanDiameter[speciesIndex].get(uc, layerType); + } + + public float getCVSmall(int speciesIndex, UtilizationClassVariable variable) { + if (!areCompatibilityVariablesSet) { + throw new IllegalStateException(UNSET_CV_BASAL_AREAS); + } + + return cvPrimaryLayerSmall[speciesIndex].get(variable); + } + +} \ No newline at end of file diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/ProcessingState.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/ProcessingState.java new file mode 100644 index 000000000..ca33f37c8 --- /dev/null +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/ProcessingState.java @@ -0,0 +1,111 @@ +package ca.bc.gov.nrs.vdyp.processing_state; + +import java.util.Map; +import java.util.Optional; + +import ca.bc.gov.nrs.vdyp.application.ProcessingException; +import ca.bc.gov.nrs.vdyp.application.RuntimeProcessingException; +import ca.bc.gov.nrs.vdyp.application.VdypApplicationIdentifier; +import ca.bc.gov.nrs.vdyp.common.ComputationMethods; +import ca.bc.gov.nrs.vdyp.common.EstimationMethods; +import ca.bc.gov.nrs.vdyp.common.Utils; +import ca.bc.gov.nrs.vdyp.controlmap.ResolvedControlMap; +import ca.bc.gov.nrs.vdyp.model.BecDefinition; +import ca.bc.gov.nrs.vdyp.model.LayerType; +import ca.bc.gov.nrs.vdyp.model.VdypLayer; +import ca.bc.gov.nrs.vdyp.model.VdypPolygon; + +public abstract class ProcessingState> { + + /** The control map defining the context of the execution */ + final RCM controlMap; + + /** The estimators instance used by this engine */ + final EstimationMethods estimators; + + /** The computation instance used by this engine */ + final ComputationMethods computers; + + /** The polygon on which the Processor is currently operating */ + private VdypPolygon polygon; + + /** The processing state of the primary layer of polygon */ + private LS plps; + + /** The processing state of the veteran layer of polygon */ + private Optional vlps; + + protected ProcessingState(Map controlMap) throws ProcessingException { + this.controlMap = resolveControlMap(controlMap); + this.estimators = new EstimationMethods(this.controlMap); + this.computers = new ComputationMethods(estimators, VdypApplicationIdentifier.VDYP_FORWARD); + } + + protected abstract RCM resolveControlMap(Map controlMap); + + protected abstract LS createLayerState(VdypPolygon polygon, VdypLayer layer) throws ProcessingException; + + private LS createLayerStateSafe(VdypPolygon polygon, VdypLayer layer) { + try { + return createLayerState(polygon, layer); + } catch (ProcessingException e) { + throw new RuntimeProcessingException(e); + } + } + + protected Optional getLayer(LayerType type) { + return Optional.ofNullable(polygon).flatMap(p -> Optional.ofNullable(p.getLayers().get(type))); + } + + public void setPolygon(VdypPolygon polygon) throws ProcessingException { + + this.polygon = polygon; + + this.plps = createLayerState( + polygon, getLayer(LayerType.PRIMARY) + .orElseThrow(() -> new IllegalStateException("No primary layer")) + ); + try { + this.vlps = getLayer(LayerType.VETERAN) + .map(layer -> createLayerStateSafe(polygon, layer)); + } catch (RuntimeProcessingException e) { + throw e.getCause(); + } + } + + /** @return the current polygon */ + public VdypPolygon getCurrentPolygon() { + return polygon; + } + + /** @return the compact form of the current polygon's identifier. Shortcut. */ + public String getCompactPolygonIdentifier() { + return polygon.getPolygonIdentifier().toStringCompact(); + } + + /** @return the starting year of the current polygon. Shortcut. */ + public int getCurrentStartingYear() { + return polygon.getPolygonIdentifier().getYear(); + } + + /** @return the bec zone of the current polygon. Shortcut. */ + public BecDefinition getCurrentBecZone() { + return polygon.getBiogeoclimaticZone(); + } + + public LS getPrimaryLayerProcessingState() { + return plps; + } + + public Optional getVeteranLayerProcessingState() { + return vlps; + } + + public VdypPolygon updatePolygon() { + + polygon.getLayers().put(LayerType.PRIMARY, plps.updateLayerFromBank()); + vlps.ifPresent(vlps -> polygon.getLayers().put(LayerType.VETERAN, vlps.updateLayerFromBank())); + + return polygon; + } +} \ No newline at end of file diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java index a122d8e52..8ea42c2b4 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java @@ -193,7 +193,7 @@ void testJustDefault() throws Exception { var outBytes = new ByteArrayOutputStream(); var outPrint = new PrintStream(outBytes); var input = new ByteArrayInputStream("\n".getBytes()); - var result = app.getControlFileNamesFromUser(outPrint, input); + List result = app.getControlFileNamesFromUser(outPrint, input); assertThat(result, Matchers.contains("default.ctl")); } @@ -202,7 +202,7 @@ void testOneEntry() throws Exception { var outBytes = new ByteArrayOutputStream(); var outPrint = new PrintStream(outBytes); var input = new ByteArrayInputStream("alternate.ctl\n".getBytes()); - var result = app.getControlFileNamesFromUser(outPrint, input); + List result = app.getControlFileNamesFromUser(outPrint, input); assertThat(result, Matchers.contains("alternate.ctl")); } @@ -211,7 +211,7 @@ void testTwoEntries() throws Exception { var outBytes = new ByteArrayOutputStream(); var outPrint = new PrintStream(outBytes); var input = new ByteArrayInputStream("alternate1.ctl alternate2.ctl\n".getBytes()); - var result = app.getControlFileNamesFromUser(outPrint, input); + List result = app.getControlFileNamesFromUser(outPrint, input); assertThat(result, Matchers.contains("alternate1.ctl", "alternate2.ctl")); } @@ -220,7 +220,7 @@ void testOneEntryPlusDefault() throws Exception { var outBytes = new ByteArrayOutputStream(); var outPrint = new PrintStream(outBytes); var input = new ByteArrayInputStream("*alternate.ctl\n".getBytes()); - var result = app.getControlFileNamesFromUser(outPrint, input); + List result = app.getControlFileNamesFromUser(outPrint, input); assertThat(result, Matchers.contains("default.ctl", "alternate.ctl")); } @@ -229,7 +229,7 @@ void testTwoEntriesPlusDefault() throws Exception { var outBytes = new ByteArrayOutputStream(); var outPrint = new PrintStream(outBytes); var input = new ByteArrayInputStream("*alternate1.ctl alternate2.ctl\n".getBytes()); - var result = app.getControlFileNamesFromUser(outPrint, input); + List result = app.getControlFileNamesFromUser(outPrint, input); assertThat(result, Matchers.contains("default.ctl", "alternate1.ctl", "alternate2.ctl")); } } diff --git a/lib/vdyp-forward/pom.xml b/lib/vdyp-forward/pom.xml index e46351603..638821348 100644 --- a/lib/vdyp-forward/pom.xml +++ b/lib/vdyp-forward/pom.xml @@ -1,10 +1,11 @@ - + 4.0.0 - + vdyp-forward jar - + Variable Density Yield Project - Forward http://maven.apache.org @@ -13,14 +14,14 @@ vdyp-lib 0.0.1-SNAPSHOT - + ca.bc.gov.nrs.vdyp vdyp-common 0.0.1-SNAPSHOT - + ca.bc.gov.nrs.vdyp vdyp-common @@ -30,8 +31,8 @@ - org.apache.commons - commons-lang3 + org.apache.commons + commons-lang3 @@ -42,7 +43,7 @@ org.slf4j slf4j-jdk14 - + org.junit.jupiter junit-jupiter-api @@ -72,7 +73,7 @@ src/main/resources/application.properties - + src/main/resources true @@ -97,8 +98,19 @@ + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + - + From 1082ad49446f04ca5d6e62e7c465de06539962ae Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Thu, 17 Oct 2024 13:10:39 -0700 Subject: [PATCH 05/22] Backprep --- .../nrs/vdyp/back/BackProcessingEngine.java | 103 +++++++++++++++++- .../bc/gov/nrs/vdyp/back/BackProcessor.java | 5 +- .../BackLayerProcessingState.java | 4 +- .../processing_state/BackProcessingState.java | 51 +++++++-- .../ca/bc/gov/nrs/vdyp/application/Pass.java | 7 +- .../gov/nrs/vdyp/application/Processor.java | 46 ++++++-- .../VdypProcessingApplication.java | 11 +- .../nrs/vdyp/io/write/VdypOutputWriter.java | 9 +- .../LayerProcessingState.java | 6 +- .../processing_state/ProcessingState.java | 15 ++- .../VdypProcessingApplicationTest.java | 24 ++-- .../nrs/vdyp/forward/ForwardProcessor.java | 102 +++-------------- .../vdyp/forward/VdypForwardApplication.java | 12 +- ...wardProcessorCheckpointGenerationTest.java | 13 +-- .../forward/ForwardProcessorEndToEndTest.java | 12 +- .../ForwardProcessorZipOutputStreamTest.java | 12 +- .../bc/gov/nrs/vdyp/vri/model/VriLayer.java | 3 +- 17 files changed, 250 insertions(+), 185 deletions(-) diff --git a/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngine.java b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngine.java index 8f1ce44a1..30ef4433e 100644 --- a/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngine.java +++ b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngine.java @@ -1,24 +1,121 @@ package ca.bc.gov.nrs.vdyp.back; +import static java.lang.Math.max; +import static java.lang.Math.min; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + import ca.bc.gov.nrs.vdyp.application.ProcessingEngine; import ca.bc.gov.nrs.vdyp.application.ProcessingException; import ca.bc.gov.nrs.vdyp.application.StandProcessingException; import ca.bc.gov.nrs.vdyp.back.processing_state.BackProcessingState; +import ca.bc.gov.nrs.vdyp.model.ComponentSizeLimits; +import ca.bc.gov.nrs.vdyp.model.LayerType; +import ca.bc.gov.nrs.vdyp.model.MatrixMap2; +import ca.bc.gov.nrs.vdyp.model.MatrixMap2Impl; +import ca.bc.gov.nrs.vdyp.model.Region; +import ca.bc.gov.nrs.vdyp.model.UtilizationClass; +import ca.bc.gov.nrs.vdyp.model.UtilizationClassVariable; +import ca.bc.gov.nrs.vdyp.model.VolumeVariable; +import ca.bc.gov.nrs.vdyp.processing_state.Bank; public class BackProcessingEngine extends ProcessingEngine { /** - * + * * @throws StandProcessingException */ // BACKPREP void prepare(BackProcessingState state) throws ProcessingException { + // Copy the basal area for the veteran layer if it exists to the polygon state + state.setBaseAreaVeteran( - state.getVeteranLayerProcessingState() - .map(vetState -> vetState.getBank().basalAreas[0][0 + 1]) + state.getVeteranLayerProcessingState().map(vetState -> vetState.getBank().basalAreas[0][0 + 1]) ); + // Copy slices of the compatibility variables for the primary layer to the polygon state + + var primaryState = state.getPrimaryLayerProcessingState(); + + int specCount = primaryState.getNSpecies(); + + @SuppressWarnings("unchecked") + MatrixMap2[] cvVolume = new MatrixMap2[specCount + 1]; + @SuppressWarnings("unchecked") + Map[] cvBasalArea = new Map[specCount + 1]; + @SuppressWarnings("unchecked") + Map[] cvQuadraticMeanDiameter = new Map[specCount + 1]; + @SuppressWarnings("unchecked") + Map[] cvPrimaryLayerSmall = new Map[specCount + 1]; + + for (int i = 0; i < primaryState.getNSpecies(); i++) { + final int specIndex = i + 1; + cvVolume[specIndex] = new MatrixMap2Impl<>( + List.of(UtilizationClass.values()), List.of(VolumeVariable.values()), + (uc, vv) -> primaryState.getCVVolume(specIndex, uc, vv, LayerType.PRIMARY) + ); + + cvBasalArea[specIndex] = new EnumMap<>(UtilizationClass.class); + cvQuadraticMeanDiameter[specIndex] = new EnumMap<>(UtilizationClass.class); + cvPrimaryLayerSmall[specIndex] = new EnumMap<>(UtilizationClassVariable.class); + + for (var uc : UtilizationClass.values()) { + cvBasalArea[specIndex].put(uc, primaryState.getCVBasalArea(specIndex, uc, LayerType.PRIMARY)); + cvQuadraticMeanDiameter[specIndex] + .put(uc, primaryState.getCVQuadraticMeanDiameter(specIndex, uc, LayerType.PRIMARY)); + } + + for (var ucv : UtilizationClassVariable.values()) { + cvPrimaryLayerSmall[specIndex].put(ucv, primaryState.getCVSmall(specIndex, ucv)); + } + + } + + state.setCompatibilityVariableDetails(cvVolume, cvBasalArea, cvQuadraticMeanDiameter, cvPrimaryLayerSmall); + + Bank primaryBank = state.getPrimaryLayerProcessingState().getBank(); + Region polygonRegion = state.getCurrentBecZone().getRegion(); + + ComponentSizeLimits[] limits = new ComponentSizeLimits[primaryState.getNSpecies() + 1]; + float[] finalDiameters = new float[primaryState.getNSpecies() + 1]; + + int ucIndexAll = UtilizationClass.ALL.ordinal(); // Intentionally use ordinal instead of index. + + finalDiameters[0] = primaryBank.quadMeanDiameters[0][ucIndexAll]; + for (int i = 0; i < primaryState.getNSpecies(); i++) { + final int specIndex = i + 1; + + finalDiameters[specIndex] = primaryBank.quadMeanDiameters[specIndex][0]; + + final var originalLimits = state.getEstimators() + .getLimitsForHeightAndDiameter(primaryBank.speciesNames[specIndex], polygonRegion); + + final float specQuadMeanDiameter = primaryBank.quadMeanDiameters[specIndex][ucIndexAll]; + final float specLoreyHeight = primaryBank.loreyHeights[specIndex][ucIndexAll]; + + final float quadMeanDiameterLoreyHeightRatio = specQuadMeanDiameter / specLoreyHeight; + + final float loreyHeightMaximum = max(originalLimits.loreyHeightMaximum(), specLoreyHeight); + final float quadMeanDiameterMaximum = max(originalLimits.quadMeanDiameterMaximum(), specQuadMeanDiameter); + + final float minQuadMeanDiameterLoreyHeightRatio = min( + originalLimits.minQuadMeanDiameterLoreyHeightRatio(), quadMeanDiameterLoreyHeightRatio + ); + final float maxQuadMeanDiameterLoreyHeightRatio = max( + originalLimits.maxQuadMeanDiameterLoreyHeightRatio(), quadMeanDiameterLoreyHeightRatio + ); + + limits[specIndex] = new ComponentSizeLimits( + loreyHeightMaximum, quadMeanDiameterMaximum, minQuadMeanDiameterLoreyHeightRatio, + maxQuadMeanDiameterLoreyHeightRatio + ); + + } + state.setLimits(limits); + state.setFinalQuadMeanDiameters(finalDiameters); } } diff --git a/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessor.java b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessor.java index f97ffabb6..f69fad3c4 100644 --- a/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessor.java +++ b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/BackProcessor.java @@ -3,12 +3,14 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import ca.bc.gov.nrs.vdyp.application.Pass; import ca.bc.gov.nrs.vdyp.application.ProcessingException; import ca.bc.gov.nrs.vdyp.application.Processor; import ca.bc.gov.nrs.vdyp.io.FileResolver; import ca.bc.gov.nrs.vdyp.io.parse.control.BaseControlParser; +import ca.bc.gov.nrs.vdyp.model.VdypPolygon; public class BackProcessor extends Processor { @@ -20,7 +22,8 @@ protected BaseControlParser getControlFileParser() { @Override public void process( - Set vdypPassSet, Map controlMap, Optional outputFileResolver + Set vdypPassSet, Map controlMap, Optional outputFileResolver, + Predicate polygonFilter ) throws ProcessingException { // TODO Auto-generated method stub diff --git a/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackLayerProcessingState.java b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackLayerProcessingState.java index de5b52a22..823759fbe 100644 --- a/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackLayerProcessingState.java +++ b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackLayerProcessingState.java @@ -11,8 +11,8 @@ import ca.bc.gov.nrs.vdyp.processing_state.LayerProcessingState; import ca.bc.gov.nrs.vdyp.processing_state.ProcessingState; -public class BackLayerProcessingState extends - LayerProcessingState { +public class BackLayerProcessingState + extends LayerProcessingState { protected BackLayerProcessingState( ProcessingState ps, VdypPolygon polygon, diff --git a/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackProcessingState.java b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackProcessingState.java index fc1cd4523..f09a3e9ef 100644 --- a/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackProcessingState.java +++ b/lib/vdyp-back/src/main/java/ca/bc/gov/nrs/vdyp/back/processing_state/BackProcessingState.java @@ -1,10 +1,14 @@ package ca.bc.gov.nrs.vdyp.back.processing_state; +import java.text.MessageFormat; import java.util.Map; import java.util.Optional; +import java.util.function.Supplier; import ca.bc.gov.nrs.vdyp.application.ProcessingException; import ca.bc.gov.nrs.vdyp.forward.controlmap.ForwardResolvedControlMap; +import ca.bc.gov.nrs.vdyp.forward.controlmap.ForwardResolvedControlMapImpl; +import ca.bc.gov.nrs.vdyp.model.ComponentSizeLimits; import ca.bc.gov.nrs.vdyp.model.MatrixMap2; import ca.bc.gov.nrs.vdyp.model.UtilizationClass; import ca.bc.gov.nrs.vdyp.model.UtilizationClassVariable; @@ -18,8 +22,12 @@ public class BackProcessingState extends ProcessingState baseAreaVeteran = Optional.empty(); // BACK1/BAV private static final String COMPATIBILITY_VARIABLES_SET_CAN_BE_SET_ONCE_ONLY = "CompatibilityVariablesSet can be set once only"; - private static final String UNSET_CV_VOLUMES = "unset cvVolumes"; - private static final String UNSET_CV_BASAL_AREAS = "unset cvBasalAreas"; + private static final Supplier UNSET_CV_VOLUMES = unset("cvVolumes"); + private static final Supplier UNSET_CV_BASAL_AREAS = unset("cvBasalAreas"); + private static final Supplier UNSET_LIMITS = unset("per species limits"); + private static final Supplier UNSET_FINAL_QUAD_MEAN_DIAMETER = unset( + "final quadratic mean diameters" + ); // Compatibility Variables - LCV1 & LCVS private boolean areCompatibilityVariablesSet = false; @@ -29,6 +37,9 @@ public class BackProcessingState extends ProcessingState[] cvQuadraticMeanDiameter; private Map[] cvPrimaryLayerSmall; + private Optional speciesLimits = Optional.empty(); + private Optional finalQuadraticMeanDiameters = Optional.empty(); + public BackProcessingState(Map controlMap) throws ProcessingException { super(controlMap); // TODO Auto-generated constructor stub @@ -36,8 +47,7 @@ public BackProcessingState(Map controlMap) throws ProcessingExce @Override public ForwardResolvedControlMap resolveControlMap(Map controlMap) { - // TODO Auto-generated method stub - return null; + return new ForwardResolvedControlMapImpl(controlMap); } @Override @@ -59,8 +69,7 @@ public Optional getBaseAreaVeteran() { } public void setCompatibilityVariableDetails( - MatrixMap2[] cvVolume, - Map[] cvBasalArea, + MatrixMap2[] cvVolume, Map[] cvBasalArea, Map[] cvQuadraticMeanDiameter, Map[] cvPrimaryLayerSmall ) { @@ -78,7 +87,7 @@ public void setCompatibilityVariableDetails( public float getCVVolume(int speciesIndex, UtilizationClass uc, VolumeVariable volumeVariable) { if (!areCompatibilityVariablesSet) { - throw new IllegalStateException(UNSET_CV_VOLUMES); + throw UNSET_CV_VOLUMES.get(); } return cvVolume[speciesIndex].get(uc, volumeVariable); @@ -86,7 +95,7 @@ public float getCVVolume(int speciesIndex, UtilizationClass uc, VolumeVariable v public float getCVBasalArea(int speciesIndex, UtilizationClass uc) { if (!areCompatibilityVariablesSet) { - throw new IllegalStateException(UNSET_CV_BASAL_AREAS); + throw UNSET_CV_BASAL_AREAS.get(); } return cvBasalArea[speciesIndex].get(uc); @@ -94,7 +103,7 @@ public float getCVBasalArea(int speciesIndex, UtilizationClass uc) { public float getCVQuadraticMeanDiameter(int speciesIndex, UtilizationClass uc) { if (!areCompatibilityVariablesSet) { - throw new IllegalStateException(UNSET_CV_BASAL_AREAS); + throw UNSET_CV_BASAL_AREAS.get(); } return cvQuadraticMeanDiameter[speciesIndex].get(uc); @@ -102,9 +111,31 @@ public float getCVQuadraticMeanDiameter(int speciesIndex, UtilizationClass uc) { public float getCVSmall(int speciesIndex, UtilizationClassVariable variable) { if (!areCompatibilityVariablesSet) { - throw new IllegalStateException(UNSET_CV_BASAL_AREAS); + throw UNSET_CV_BASAL_AREAS.get(); } return cvPrimaryLayerSmall[speciesIndex].get(variable); } + + public void setLimits(ComponentSizeLimits[] limits) { + this.speciesLimits = Optional.of(limits); + } + + public ComponentSizeLimits getLimits(int speciesIndex) { + return this.speciesLimits.orElseThrow(UNSET_LIMITS)[speciesIndex]; + } + + public float getFinalQuadraticMeanDiameter(int speciesIndex) { + return this.finalQuadraticMeanDiameters.orElseThrow(UNSET_FINAL_QUAD_MEAN_DIAMETER)[speciesIndex]; + } + + protected static Supplier unset(final String field) { + final String message = MessageFormat.format("unset {0}", field); + return () -> new IllegalStateException(message); + } + + public void setFinalQuadMeanDiameters(float[] finalDiameters) { + finalQuadraticMeanDiameters = Optional.of(finalDiameters); + } + } diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Pass.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Pass.java index 14c74412a..16e60d9ee 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Pass.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Pass.java @@ -1,11 +1,8 @@ package ca.bc.gov.nrs.vdyp.application; public enum Pass { - INITIALIZE("Perform Initiation activities"), - OPEN_FILES("Open the stand data files"), - PROCESS_STANDS("Process stands"), - MULTIPLE_STANDS("Allow multiple polygons"), - CLOSE_FILES("Close data files"), + INITIALIZE("Perform Initiation activities"), OPEN_FILES("Open the stand data files"), + PROCESS_STANDS("Process stands"), MULTIPLE_STANDS("Allow multiple polygons"), CLOSE_FILES("Close data files"), ADDITIONAL_BASE_AREA_CRITERIA("Impose additional base area criteria"); final String description; diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java index 569413ca4..347373fde 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java @@ -8,6 +8,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,12 +17,24 @@ 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.model.VdypPolygon; /** * - * The overall algorithm of a VDYP Application. + * The common code for the algorithmic part of a forward or backward growth predictor * + * VDYPPASS IN/OUT I*4(10) Major Control Functions + *

    + *
  1. IN Perform Initiation activities? (0=No, 1=Yes) + *
  2. IN Open the stand data files (0=No, 1=Yes) + *
  3. IN Process stands (0=No, 1=Yes) + *
  4. IN Allow multiple polygons (0=No, 1=Yes) (Subset of stand processing. May limit to 1 stand) + *
+ * + * @author Michael Junkin, Vivid Solutions + * @author Kevin Smith, Vivid Solutions */ + public abstract class Processor { private static final Logger logger = LoggerFactory.getLogger(Processor.class); @@ -65,13 +78,13 @@ public void run( /** * Get a parser for the control file for this application - * + * * @return */ protected abstract BaseControlParser getControlFileParser(); /** - * @return all possible values of the pass enum + * @return all values of the pass enum applicable for this processor */ protected Set getAllPasses() { return EnumSet.allOf(Pass.class); @@ -79,25 +92,42 @@ protected Set getAllPasses() { /** * Log the settings of the pass set - * + * * @param vdypPassSet */ protected void logPass(Set active) { logger.atInfo().addArgument(active).setMessage("VDYPPASS: {}").log(); for (var pass : getAllPasses()) { String activeIndicator = active.contains(pass) ? "☑" : "☐"; - logger.atDebug().addArgument(activeIndicator).addArgument(pass.toString()).addArgument(pass.getDescription()); + logger.atDebug().addArgument(activeIndicator).addArgument(pass.toString()) + .addArgument(pass.getDescription()); } } /** - * Implements + * Implements the process * - * @param outputFileResolver + * @param vdypPassSet Phases of the process to implement + * @param controlMap Control map to configure the process + * @param outputFileResolver File resolver for the output + */ + public void + process(Set vdypPassSet, Map controlMap, Optional outputFileResolver) + throws ProcessingException { + process(vdypPassSet, controlMap, outputFileResolver, p -> true); + } + + /** + * Implements the process * + * @param vdypPassSet Phases of the process to implement + * @param controlMap Control map to configure the process + * @param outputFileResolver File resolver for the output + * @param polygonFilter A polygon will be processed if and only if this returns true for that polygon * @throws ProcessingException */ public abstract void process( - Set vdypPassSet, Map controlMap, Optional outputFileResolver + Set vdypPassSet, Map controlMap, Optional outputFileResolver, + Predicate polygonFilter ) throws ProcessingException; } diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java index 2a5ec2b15..0e700731a 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java @@ -23,9 +23,8 @@ public abstract class VdypProcessingApplication

extends Vdy @SuppressWarnings("java:S106") protected static void initLogging(Class klazz) { try { - LogManager.getLogManager().readConfiguration( - klazz.getClassLoader().getResourceAsStream("logging.properties") - ); + LogManager.getLogManager() + .readConfiguration(klazz.getClassLoader().getResourceAsStream("logging.properties")); } catch (SecurityException | IOException e) { System.err.println("Unable to configure logging system"); } @@ -90,10 +89,8 @@ public List getControlFileNamesFromUser(final PrintStream os, final Inpu final String defaultFilename = getDefaultControlFileName(); List controlFileNames; os.print( - MessageFormat.format( - "Enter name of control file (or RETURN for {1}) or *name for both): ", - defaultFilename - ) + MessageFormat + .format("Enter name of control file (or RETURN for {1}) or *name for both): ", defaultFilename) ); controlFileNames = new ArrayList<>(); 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 88d5095e2..e6fe8e70d 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 @@ -165,8 +165,9 @@ public void writePolygonWithSpeciesAndUtilization(VdypPolygon polygon) throws IO // The original VDYP7 system performs this task at this location, storing the result in // a separate COMMON. Here, we store the result in the Polygon, knowing that the originally // calculated values are not being used. - sortedLayers.stream() - .forEach(l -> calculateCuVolumeLessDecayWastageBreakage(controlMap, l, polygon.getBiogeoclimaticZone())); + sortedLayers.stream().forEach( + l -> calculateCuVolumeLessDecayWastageBreakage(controlMap, l, polygon.getBiogeoclimaticZone()) + ); for (var layer : sortedLayers) { writeUtilization(polygon, layer, layer); @@ -182,7 +183,9 @@ public void writePolygonWithSpeciesAndUtilization(VdypPolygon polygon) throws IO writeUtilizationEndRecord(polygon); } - static void calculateCuVolumeLessDecayWastageBreakage(ResolvedControlMap controlMap, VdypLayer layer, BecDefinition bec) { + static void calculateCuVolumeLessDecayWastageBreakage( + ResolvedControlMap controlMap, VdypLayer layer, BecDefinition bec + ) { // Technically, BreakageEquationGroups are not required. If missing, it will not be // possible for this method to do its work; but, it's still not an error. diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/LayerProcessingState.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/LayerProcessingState.java index 79d64fb4d..1ba0d8e4d 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/LayerProcessingState.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/LayerProcessingState.java @@ -61,10 +61,7 @@ protected LayerProcessingState(ProcessingState ps, VdypPolygon polygo BecDefinition becZone = polygon.getBiogeoclimaticZone(); - this.bank = new Bank( - polygon.getLayers().get(subjectLayerType), becZone, - getBankFilter() - ); + this.bank = new Bank(polygon.getLayers().get(subjectLayerType), becZone, getBankFilter()); } @@ -82,7 +79,6 @@ public BecDefinition getBecZone() { return bank.getBecZone(); } - public static Logger getLogger() { return logger; } diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/ProcessingState.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/ProcessingState.java index ca33f37c8..34bb91d59 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/ProcessingState.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/ProcessingState.java @@ -8,7 +8,6 @@ import ca.bc.gov.nrs.vdyp.application.VdypApplicationIdentifier; import ca.bc.gov.nrs.vdyp.common.ComputationMethods; import ca.bc.gov.nrs.vdyp.common.EstimationMethods; -import ca.bc.gov.nrs.vdyp.common.Utils; import ca.bc.gov.nrs.vdyp.controlmap.ResolvedControlMap; import ca.bc.gov.nrs.vdyp.model.BecDefinition; import ca.bc.gov.nrs.vdyp.model.LayerType; @@ -23,6 +22,14 @@ public abstract class ProcessingState new IllegalStateException("No primary layer")) + polygon, getLayer(LayerType.PRIMARY).orElseThrow(() -> new IllegalStateException("No primary layer")) ); try { - this.vlps = getLayer(LayerType.VETERAN) - .map(layer -> createLayerStateSafe(polygon, layer)); + this.vlps = getLayer(LayerType.VETERAN).map(layer -> createLayerStateSafe(polygon, layer)); } catch (RuntimeProcessingException e) { throw e.getCause(); } diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java index 8ea42c2b4..2ff829e0d 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java @@ -59,10 +59,8 @@ void testCommandLineControlNoError() throws Exception { var input = new ByteArrayInputStream("\n".getBytes()); processor.run( - EasyMock.isA(FileResolver.class), - EasyMock.isA(FileResolver.class), - EasyMock.eq(List.of("argument.ctl")), - EasyMock.eq(EnumSet.allOf(Pass.class)) + EasyMock.isA(FileResolver.class), EasyMock.isA(FileResolver.class), + EasyMock.eq(List.of("argument.ctl")), EasyMock.eq(EnumSet.allOf(Pass.class)) ); EasyMock.expectLastCall().once(); @@ -82,10 +80,8 @@ void testMultipleCommandLineControlNoError() throws Exception { var input = new ByteArrayInputStream("\n".getBytes()); processor.run( - EasyMock.isA(FileResolver.class), - EasyMock.isA(FileResolver.class), - EasyMock.eq(List.of("argument1.ctl", "argument2.ctl")), - EasyMock.eq(EnumSet.allOf(Pass.class)) + EasyMock.isA(FileResolver.class), EasyMock.isA(FileResolver.class), + EasyMock.eq(List.of("argument1.ctl", "argument2.ctl")), EasyMock.eq(EnumSet.allOf(Pass.class)) ); EasyMock.expectLastCall().once(); @@ -105,10 +101,8 @@ void testConsoleInputControlNoError() throws Exception { var input = new ByteArrayInputStream("alternate1.ctl alternate2.ctl\n".getBytes()); processor.run( - EasyMock.isA(FileResolver.class), - EasyMock.isA(FileResolver.class), - EasyMock.eq(List.of("alternate1.ctl", "alternate2.ctl")), - EasyMock.eq(EnumSet.allOf(Pass.class)) + EasyMock.isA(FileResolver.class), EasyMock.isA(FileResolver.class), + EasyMock.eq(List.of("alternate1.ctl", "alternate2.ctl")), EasyMock.eq(EnumSet.allOf(Pass.class)) ); EasyMock.expectLastCall().once(); @@ -142,10 +136,8 @@ void testErrorWhileProcessing() throws Exception { var input = new ByteArrayInputStream("\n".getBytes()); processor.run( - EasyMock.isA(FileResolver.class), - EasyMock.isA(FileResolver.class), - EasyMock.eq(List.of("argument.ctl")), - EasyMock.eq(EnumSet.allOf(Pass.class)) + EasyMock.isA(FileResolver.class), EasyMock.isA(FileResolver.class), + EasyMock.eq(List.of("argument.ctl")), EasyMock.eq(EnumSet.allOf(Pass.class)) ); EasyMock.expectLastCall().andThrow(new ProcessingException("Test")).once(); diff --git a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessor.java b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessor.java index 32f2c6d35..2a949f00a 100644 --- a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessor.java +++ b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessor.java @@ -1,9 +1,6 @@ package ca.bc.gov.nrs.vdyp.forward; import java.io.IOException; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -12,11 +9,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ca.bc.gov.nrs.vdyp.application.Pass; import ca.bc.gov.nrs.vdyp.application.ProcessingException; +import ca.bc.gov.nrs.vdyp.application.Processor; import ca.bc.gov.nrs.vdyp.common.ControlKey; import ca.bc.gov.nrs.vdyp.io.FileResolver; -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.write.VdypOutputWriter; import ca.bc.gov.nrs.vdyp.model.VdypPolygon; @@ -48,87 +46,12 @@ * * * @author Michael Junkin, Vivid Solutions + * @author Kevin Smith, Vivid Solutions */ -public class ForwardProcessor { +public class ForwardProcessor extends Processor { private static final Logger logger = LoggerFactory.getLogger(ForwardProcessor.class); - /** - * Initialize VdypForwardProcessor - * - * @param inputFileResolver - * @param outputFileResolver - * @param controlFileNames - * - * @throws IOException - * @throws ResourceParseException - * @throws ProcessingException - */ - void run( - FileResolver inputFileResolver, FileResolver outputFileResolver, List controlFileNames, - Set vdypPassSet - ) throws IOException, ResourceParseException, ProcessingException { - run(inputFileResolver, outputFileResolver, controlFileNames, vdypPassSet, (p) -> true); - } - - /** - * Initialize VdypForwardProcessor - * - * @param inputFileResolver - * @param outputFileResolver - * @param controlFileNames - * - * @throws IOException - * @throws ResourceParseException - * @throws ProcessingException - */ - void run( - FileResolver inputFileResolver, FileResolver outputFileResolver, List controlFileNames, - Set vdypPassSet, Predicate polygonFilter - ) throws IOException, ResourceParseException, ProcessingException { - - logger.info("VDYPPASS: {}", vdypPassSet); - logger.debug("VDYPPASS(1): Perform Initiation activities?"); - logger.debug("VDYPPASS(2): Open the stand data files"); - logger.debug("VDYPPASS(3): Process stands"); - logger.debug("VDYPPASS(4): Allow multiple polygons"); - logger.debug("VDYPPASS(5): Close data files"); - logger.debug(" "); - - // Load the control map - Map controlMap = new HashMap<>(); - - var parser = new ForwardControlParser(); - - for (var controlFileName : controlFileNames) { - logger.info("Resolving and parsing {}", controlFileName); - - try (var is = inputFileResolver.resolveForInput(controlFileName)) { - Path controlFilePath = inputFileResolver.toPath(controlFileName).getParent(); - FileSystemFileResolver relativeResolver = new FileSystemFileResolver(controlFilePath); - - parser.parse(is, relativeResolver, controlMap); - } - } - - process(vdypPassSet, controlMap, Optional.of(outputFileResolver), polygonFilter); - } - - /** - * Implements VDYP_SUB. - * - * @param vdypPassSet the set of stages (passes) to be executed - * @param controlMap parsed control map - * @param outputFileResolver optional file resolver that, if present, locates output files. - * - * @throws ProcessingException - */ - public void process( - Set vdypPassSet, Map controlMap, Optional outputFileResolver - ) throws ProcessingException { - process(vdypPassSet, controlMap, outputFileResolver, (p) -> true); - } - /** * Implements VDYP_SUB, excluding all polygons that don't pass the given polygonFilter. * @@ -139,15 +62,17 @@ public void process( * * @throws ProcessingException */ + @Override + public void process( - Set vdypPassSet, Map controlMap, Optional outputFileResolver, + Set vdypPassSet, Map controlMap, Optional outputFileResolver, Predicate polygonFilter ) throws ProcessingException { logger.info("Beginning processing with given configuration"); int maxPoly = 0; - if (vdypPassSet.contains(ForwardPass.PASS_1)) { + if (vdypPassSet.contains(Pass.INITIALIZE)) { Object maxPolyValue = controlMap.get(ControlKey.MAX_NUM_POLY.name()); if (maxPolyValue != null) { maxPoly = (Integer) maxPolyValue; @@ -156,12 +81,12 @@ public void process( logger.debug("MaxPoly: {}", maxPoly); - if (vdypPassSet.contains(ForwardPass.PASS_2)) { + if (vdypPassSet.contains(Pass.OPEN_FILES)) { // input files are already opened // TODO: open output files } - if (vdypPassSet.contains(ForwardPass.PASS_3)) { + if (vdypPassSet.contains(Pass.PROCESS_STANDS)) { Optional outputWriter = Optional.empty(); @@ -210,4 +135,9 @@ public void process( }); } } + + @Override + protected BaseControlParser getControlFileParser() { + return new ForwardControlParser(); + } } diff --git a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/VdypForwardApplication.java b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/VdypForwardApplication.java index d149f83a7..06b506be0 100644 --- a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/VdypForwardApplication.java +++ b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/VdypForwardApplication.java @@ -1,17 +1,11 @@ package ca.bc.gov.nrs.vdyp.forward; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_1; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_2; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_3; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_4; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_5; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; +import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.logging.LogManager; @@ -19,6 +13,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ca.bc.gov.nrs.vdyp.application.Pass; import ca.bc.gov.nrs.vdyp.application.VdypApplication; import ca.bc.gov.nrs.vdyp.application.VdypApplicationIdentifier; import ca.bc.gov.nrs.vdyp.io.FileSystemFileResolver; @@ -42,7 +37,8 @@ public class VdypForwardApplication extends VdypApplication { public static final String DEFAULT_VDYP_CONTROL_FILE_NAME = "vdyp.ctr"; - private static Set vdypPassSet = new HashSet<>(Arrays.asList(PASS_1, PASS_2, PASS_3, PASS_4, PASS_5)); + private static Set vdypPassSet = EnumSet + .of(Pass.INITIALIZE, Pass.OPEN_FILES, Pass.PROCESS_STANDS, Pass.MULTIPLE_STANDS); public static void main(final String... args) { diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorCheckpointGenerationTest.java b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorCheckpointGenerationTest.java index e05e46e7e..6df64445a 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorCheckpointGenerationTest.java +++ b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorCheckpointGenerationTest.java @@ -1,18 +1,11 @@ package ca.bc.gov.nrs.vdyp.forward; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_1; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_2; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_3; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_4; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_5; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -22,6 +15,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ca.bc.gov.nrs.vdyp.application.Pass; import ca.bc.gov.nrs.vdyp.application.ProcessingException; import ca.bc.gov.nrs.vdyp.common.ControlKey; import ca.bc.gov.nrs.vdyp.forward.parsers.VdypPolygonParser; @@ -40,7 +34,8 @@ class ForwardProcessorCheckpointGenerationTest { @SuppressWarnings("unused") private static final Logger logger = LoggerFactory.getLogger(ForwardProcessorCheckpointGenerationTest.class); - private static Set vdypPassSet = new HashSet<>(Arrays.asList(PASS_1, PASS_2, PASS_3, PASS_4, PASS_5)); + private static Set vdypPassSet = EnumSet + .of(Pass.INITIALIZE, Pass.OPEN_FILES, Pass.PROCESS_STANDS, Pass.MULTIPLE_STANDS); @Test void test() throws IOException, ResourceParseException, ProcessingException { diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorEndToEndTest.java b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorEndToEndTest.java index 5beb3e84e..3e16c1843 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorEndToEndTest.java +++ b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorEndToEndTest.java @@ -1,10 +1,5 @@ package ca.bc.gov.nrs.vdyp.forward; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_1; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_2; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_3; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_4; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_5; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -12,10 +7,9 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -25,6 +19,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ca.bc.gov.nrs.vdyp.application.Pass; import ca.bc.gov.nrs.vdyp.application.ProcessingException; import ca.bc.gov.nrs.vdyp.common.ControlKey; import ca.bc.gov.nrs.vdyp.forward.parsers.VdypPolygonParser; @@ -52,7 +47,8 @@ class ForwardProcessorEndToEndTest { @SuppressWarnings("unused") private static final Logger logger = LoggerFactory.getLogger(ForwardProcessorEndToEndTest.class); - private static Set vdypPassSet = new HashSet<>(Arrays.asList(PASS_1, PASS_2, PASS_3, PASS_4, PASS_5)); + private static Set vdypPassSet = EnumSet + .of(Pass.INITIALIZE, Pass.OPEN_FILES, Pass.PROCESS_STANDS, Pass.MULTIPLE_STANDS); private int nEquals = 0; private int nWithin1Percent = 0; diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorZipOutputStreamTest.java b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorZipOutputStreamTest.java index f50182e46..4f4790f6e 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorZipOutputStreamTest.java +++ b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorZipOutputStreamTest.java @@ -1,10 +1,5 @@ package ca.bc.gov.nrs.vdyp.forward; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_1; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_2; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_3; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_4; -import static ca.bc.gov.nrs.vdyp.forward.ForwardPass.PASS_5; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -13,8 +8,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; -import java.util.HashSet; +import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.zip.ZipFile; @@ -24,6 +18,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ca.bc.gov.nrs.vdyp.application.Pass; import ca.bc.gov.nrs.vdyp.application.ProcessingException; import ca.bc.gov.nrs.vdyp.io.FileResolver; import ca.bc.gov.nrs.vdyp.io.ZipOutputFileResolver; @@ -34,7 +29,8 @@ class ForwardProcessorZipOutputStreamTest { private static final Logger logger = LoggerFactory.getLogger(ForwardProcessorZipOutputStreamTest.class); - private static Set vdypPassSet = new HashSet<>(Arrays.asList(PASS_1, PASS_2, PASS_3, PASS_4, PASS_5)); + private static Set vdypPassSet = EnumSet + .of(Pass.INITIALIZE, Pass.OPEN_FILES, Pass.PROCESS_STANDS, Pass.MULTIPLE_STANDS); @TempDir Path outputFilesLocation; 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 6c35cde2d..3d8e3692b 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,8 @@ protected void check(Collection errors) { @Override protected VriLayer doBuild() { - // We want the reciprocal of the forest fraction. When we output at the other end the derived values are multiplied by the forest fraction canceling this out. + // We want the reciprocal of the forest fraction. When we output at the other end the derived values are + // multiplied by the forest fraction canceling this out. float multiplier = 100f / percentAvailable.orElse(100f); VriLayer result = new VriLayer( polygonIdentifier.get(), // From f8f55aa187bdc11ad320a985f369e48c1883dce4 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Thu, 17 Oct 2024 13:44:06 -0700 Subject: [PATCH 06/22] Consolidate Bank in common --- .../gov/nrs/vdyp/processing_state/Bank.java | 8 +- .../java/ca/bc/gov/nrs/vdyp/forward/Bank.java | 465 ------------------ .../vdyp/forward/ForwardProcessingEngine.java | 1 + .../vdyp/forward/LayerProcessingState.java | 1 + .../ca/bc/gov/nrs/vdyp/forward/BankTest.java | 1 + .../forward/Grow10StoreSpeciesDetails.java | 1 + 6 files changed, 8 insertions(+), 469 deletions(-) delete mode 100644 lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/Bank.java diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/Bank.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/Bank.java index 364abf310..781a7a70f 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/Bank.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/Bank.java @@ -146,11 +146,11 @@ public int getNSpecies() { return nSpecies; } - int[] getIndices() { + public int[] getIndices() { return indices; } - BecDefinition getBecZone() { + public BecDefinition getBecZone() { return becZone; } @@ -162,7 +162,7 @@ BecDefinition getBecZone() { * @param layer a (presumably modified) version of the layer. * @throws ProcessingException */ - void refreshBank(VdypLayer layer) throws ProcessingException { + public void refreshBank(VdypLayer layer) throws ProcessingException { if (!this.layer.equals(layer)) { throw new IllegalArgumentException( @@ -365,7 +365,7 @@ private float sumSpeciesUtilizationClassValues(float[][] ucValues, UtilizationCl * * @return as described */ - VdypLayer buildLayerFromBank() { + public VdypLayer buildLayerFromBank() { transferUtilizationsFromBank(0, layer); diff --git a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/Bank.java b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/Bank.java deleted file mode 100644 index 7728c2a4f..000000000 --- a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/Bank.java +++ /dev/null @@ -1,465 +0,0 @@ -package ca.bc.gov.nrs.vdyp.forward; - -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.IntStream; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ca.bc.gov.nrs.vdyp.application.ProcessingException; -import ca.bc.gov.nrs.vdyp.common.Utils; -import ca.bc.gov.nrs.vdyp.common_calculators.BaseAreaTreeDensityDiameter; -import ca.bc.gov.nrs.vdyp.model.BecDefinition; -import ca.bc.gov.nrs.vdyp.model.Sp64DistributionSet; -import ca.bc.gov.nrs.vdyp.model.UtilizationClass; -import ca.bc.gov.nrs.vdyp.model.VdypEntity; -import ca.bc.gov.nrs.vdyp.model.VdypLayer; -import ca.bc.gov.nrs.vdyp.model.VdypSite; -import ca.bc.gov.nrs.vdyp.model.VdypSpecies; -import ca.bc.gov.nrs.vdyp.model.VdypUtilizationHolder; - -class Bank { - - @SuppressWarnings("unused") - private static final Logger logger = LoggerFactory.getLogger(Bank.class); - - private static final int N_UTILIZATION_CLASSES = UtilizationClass.values().length; - - private final VdypLayer layer; - private final BecDefinition becZone; - - /** - * The number of species in the state. Note that all arrays have this value plus one elements in them; the element - * at index 0 is unused for the species values* and contains the default utilization in the Utilization values. - * - * (*) except: siteCurveNumbers[0] is used to store the site curve of the primary species. - */ - private int nSpecies; // BANK1 NSPB - private int[] indices; - - // Species information - - public final String[/* nSpecies + 1 */] speciesNames; // BANK2 SP0B - public final Sp64DistributionSet[/* nSpecies + 1 */] sp64Distributions; // BANK2 SP64DISTB - public final float[/* nSpecies + 1 */] siteIndices; // BANK3 SIB - public final float[/* nSpecies + 1 */] dominantHeights; // BANK3 HDB - public final float[/* nSpecies + 1 */] ageTotals; // BANK3 AGETOTB - public final float[/* nSpecies + 1 */] yearsAtBreastHeight; // BANK3 AGEBHB - public final float[/* nSpecies + 1 */] yearsToBreastHeight; // BANK3 YTBHB - public final int[/* nSpecies + 1 */] siteCurveNumbers; // BANK3 SCNB - public final int[/* nSpecies + 1 */] speciesIndices; // BANK1 ISPB - public final float[/* nSpecies + 1 */] percentagesOfForestedLand; // BANK1 PCTB - - // Utilization information, per Species - - public final float[/* nSpecies + 1, including 0 */][/* all ucs */] basalAreas; // BANK1 BAB. Units: m^2/hectare - public final float[/* nSpecies + 1, including 0 */][/* all ucs */] closeUtilizationVolumes; // BANK1 VOLCUB - public final float[/* nSpecies + 1, including 0 */][/* all ucs */] cuVolumesMinusDecay; // BANK1 VOL_DB - public final float[/* nSpecies + 1, including 0 */][/* all ucs */] cuVolumesMinusDecayAndWastage; // BANK1 VOL_DW_B - public final float[/* nSpecies + 1, including 0 */][/* uc -1 and 0 only */] loreyHeights; // BANK1 HLB - public final float[/* nSpecies + 1, including 0 */][/* all ucs */] quadMeanDiameters; // BANK1 DQB - public final float[/* nSpecies + 1, including 0 */][/* all ucs */] treesPerHectare; // BANK1 TPHB - public final float[/* nSpecies + 1, including 0 */][/* all ucs */] wholeStemVolumes; // BANK1 VOLWSB - - public Bank(VdypLayer layer, BecDefinition becZone, Predicate retainCriteria) { - - this.layer = layer; - this.becZone = becZone; - - List speciesToRetain = layer.getSpecies().values().stream().filter(s -> retainCriteria.test(s)) - .sorted((s1, s2) -> s1.getGenusIndex() - s2.getGenusIndex()).toList(); - - this.nSpecies = speciesToRetain.size(); - this.indices = IntStream.range(1, nSpecies + 1).toArray(); - - // In the following, index 0 is unused - speciesNames = new String[nSpecies + 1]; - sp64Distributions = new Sp64DistributionSet[getNSpecies() + 1]; - siteIndices = new float[nSpecies + 1]; - dominantHeights = new float[nSpecies + 1]; - ageTotals = new float[nSpecies + 1]; - yearsAtBreastHeight = new float[nSpecies + 1]; - yearsToBreastHeight = new float[nSpecies + 1]; - siteCurveNumbers = new int[nSpecies + 1]; - speciesIndices = new int[nSpecies + 1]; - percentagesOfForestedLand = new float[nSpecies + 1]; - - // In the following, index 0 is used for the default species utilization - basalAreas = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; - closeUtilizationVolumes = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; - cuVolumesMinusDecay = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; - cuVolumesMinusDecayAndWastage = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; - loreyHeights = new float[nSpecies + 1][2]; - quadMeanDiameters = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; - treesPerHectare = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; - wholeStemVolumes = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; - - int nextSlot = 1; - for (VdypSpecies s : speciesToRetain) { - transferSpeciesIntoBank(nextSlot++, s); - } - - transferUtilizationSetIntoBank(0, layer); - - // BANKCHK1 - calculate UC All values from components (rather than rely on the values - // provided in the input.) - - setCalculateUtilizationClassAllValues(); - } - - public Bank(Bank source) { - - this.becZone = source.becZone; - this.layer = source.layer; - - this.nSpecies = source.nSpecies; - this.indices = copy(source.indices); - this.speciesNames = copy(source.speciesNames); - this.speciesIndices = copy(source.speciesIndices); - - this.siteCurveNumbers = copy(source.siteCurveNumbers); - this.sp64Distributions = copy(source.sp64Distributions); - - this.ageTotals = copy(source.ageTotals); - this.dominantHeights = copy(source.dominantHeights); - this.percentagesOfForestedLand = copy(source.percentagesOfForestedLand); - this.siteIndices = copy(source.siteIndices); - this.yearsAtBreastHeight = copy(source.yearsAtBreastHeight); - this.yearsToBreastHeight = copy(source.yearsToBreastHeight); - - this.basalAreas = copy(source.basalAreas); - this.closeUtilizationVolumes = copy(source.closeUtilizationVolumes); - this.cuVolumesMinusDecay = copy(source.cuVolumesMinusDecay); - this.cuVolumesMinusDecayAndWastage = copy(source.cuVolumesMinusDecayAndWastage); - this.loreyHeights = copy(source.loreyHeights); - this.quadMeanDiameters = copy(source.quadMeanDiameters); - this.treesPerHectare = copy(source.treesPerHectare); - this.wholeStemVolumes = copy(source.wholeStemVolumes); - } - - public int getNSpecies() { - return nSpecies; - } - - int[] getIndices() { - return indices; - } - - BecDefinition getBecZone() { - return becZone; - } - - /** - * Refresh the values in the bank with an updated version (given) of the layer used to create the bank. The - * modifications cannot include any changes to the set of species, although the details of those species may of - * course change. - * - * @param layer a (presumably modified) version of the layer. - * @throws ProcessingException - */ - void refreshBank(VdypLayer layer) throws ProcessingException { - - if (!this.layer.equals(layer)) { - throw new IllegalArgumentException( - MessageFormat.format( - "One cannot refresh a bank from a" - + " layer ({0}) different from the one used to create the bank ({1})", - this.layer, layer - ) - ); - } - - List species = layer.getSpecies().values().stream() - .sorted((s1, s2) -> s1.getGenusIndex() - s2.getGenusIndex()).toList(); - - transferUtilizationSetIntoBank(0, layer); - - int nextSlot = 1; - for (VdypSpecies s : species) { - transferSpeciesIntoBank(nextSlot++, s); - } - } - - private void transferSpeciesIntoBank(int index, VdypSpecies species) { - - speciesNames[index] = species.getGenus(); - sp64Distributions[index] = species.getSp64DistributionSet(); - speciesIndices[index] = species.getGenusIndex(); - - species.getSite().ifPresentOrElse(s -> { - siteIndices[index] = s.getSiteIndex().orElse(VdypEntity.MISSING_FLOAT_VALUE); - dominantHeights[index] = s.getHeight().orElse(VdypEntity.MISSING_FLOAT_VALUE); - ageTotals[index] = s.getAgeTotal().orElse(VdypEntity.MISSING_FLOAT_VALUE); - yearsToBreastHeight[index] = s.getYearsToBreastHeight().orElse(VdypEntity.MISSING_FLOAT_VALUE); - if (ageTotals[index] != VdypEntity.MISSING_FLOAT_VALUE - && yearsToBreastHeight[index] != VdypEntity.MISSING_FLOAT_VALUE) { - yearsAtBreastHeight[index] = ageTotals[index] - yearsToBreastHeight[index]; - } else { - yearsAtBreastHeight[index] = VdypEntity.MISSING_FLOAT_VALUE; - } - siteCurveNumbers[index] = s.getSiteCurveNumber().orElse(VdypEntity.MISSING_INTEGER_VALUE); - // percentForestedLand is output-only and so not assigned here. - }, () -> { - siteIndices[index] = VdypEntity.MISSING_FLOAT_VALUE; - dominantHeights[index] = VdypEntity.MISSING_FLOAT_VALUE; - ageTotals[index] = VdypEntity.MISSING_FLOAT_VALUE; - yearsToBreastHeight[index] = VdypEntity.MISSING_FLOAT_VALUE; - yearsAtBreastHeight[index] = VdypEntity.MISSING_FLOAT_VALUE; - siteCurveNumbers[index] = VdypEntity.MISSING_INTEGER_VALUE; - }); - - transferUtilizationSetIntoBank(index, species); - } - - private void transferUtilizationSetIntoBank(int index, VdypUtilizationHolder uh) { - - for (UtilizationClass uc : UtilizationClass.values()) { - int ucIndex = uc.ordinal(); - basalAreas[index][ucIndex] = uh.getBaseAreaByUtilization().get(uc); - closeUtilizationVolumes[index][ucIndex] = uh.getCloseUtilizationVolumeByUtilization().get(uc); - cuVolumesMinusDecay[index][ucIndex] = uh.getCloseUtilizationVolumeNetOfDecayByUtilization().get(uc); - cuVolumesMinusDecayAndWastage[index][ucIndex] = uh - .getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization().get(uc); - if (ucIndex < 2 /* only uc 0 and 1 have a lorey height */) { - loreyHeights[index][ucIndex] = uh.getLoreyHeightByUtilization().get(uc); - } - quadMeanDiameters[index][ucIndex] = uh.getQuadraticMeanDiameterByUtilization().get(uc); - treesPerHectare[index][ucIndex] = uh.getTreesPerHectareByUtilization().get(uc); - wholeStemVolumes[index][ucIndex] = uh.getWholeStemVolumeByUtilization().get(uc); - } - } - - /** - * For each species, set uc All to the sum of the UC values, UC 7.5 and above only, for the summable values, and - * calculate quad-mean-diameter from these values. - *

- * For the layer, set uc All values (for summable types) to the sum of those of the individual species and set the - * other uc values to the sum of those of the individual species. Calculate the uc All value for quad-mean-diameter, - * and the uc All and Small value for lorey-height. - */ - private void setCalculateUtilizationClassAllValues() { - - int layerIndex = 0; - int ucAllIndex = UtilizationClass.ALL.ordinal(); - int ucSmallIndex = UtilizationClass.SMALL.ordinal(); - - // Each species - - for (int sp0Index : indices) { - - basalAreas[sp0Index][ucAllIndex] = sumUtilizationClassValues( - basalAreas[sp0Index], UtilizationClass.UTIL_CLASSES - ); - treesPerHectare[sp0Index][ucAllIndex] = sumUtilizationClassValues( - treesPerHectare[sp0Index], UtilizationClass.UTIL_CLASSES - ); - wholeStemVolumes[sp0Index][ucAllIndex] = sumUtilizationClassValues( - wholeStemVolumes[sp0Index], UtilizationClass.UTIL_CLASSES - ); - closeUtilizationVolumes[sp0Index][ucAllIndex] = sumUtilizationClassValues( - closeUtilizationVolumes[sp0Index], UtilizationClass.UTIL_CLASSES - ); - cuVolumesMinusDecay[sp0Index][ucAllIndex] = sumUtilizationClassValues( - cuVolumesMinusDecay[sp0Index], UtilizationClass.UTIL_CLASSES - ); - cuVolumesMinusDecayAndWastage[sp0Index][ucAllIndex] = sumUtilizationClassValues( - cuVolumesMinusDecayAndWastage[sp0Index], UtilizationClass.UTIL_CLASSES - ); - - if (basalAreas[sp0Index][ucAllIndex] > 0.0f) { - quadMeanDiameters[sp0Index][ucAllIndex] = BaseAreaTreeDensityDiameter - .quadMeanDiameter(basalAreas[sp0Index][ucAllIndex], treesPerHectare[sp0Index][ucAllIndex]); - } - } - - // Layer - - basalAreas[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues(basalAreas, UtilizationClass.ALL); - treesPerHectare[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues( - treesPerHectare, UtilizationClass.ALL - ); - wholeStemVolumes[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues( - wholeStemVolumes, UtilizationClass.ALL - ); - closeUtilizationVolumes[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues( - closeUtilizationVolumes, UtilizationClass.ALL - ); - cuVolumesMinusDecay[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues( - cuVolumesMinusDecay, UtilizationClass.ALL - ); - cuVolumesMinusDecayAndWastage[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues( - cuVolumesMinusDecayAndWastage, UtilizationClass.ALL - ); - - // Calculate the layer's uc All values for quad-mean-diameter and lorey height - - float sumLoreyHeightByBasalAreaSmall = 0.0f; - float sumBasalAreaSmall = 0.0f; - float sumLoreyHeightByBasalAreaAll = 0.0f; - - for (int sp0Index : indices) { - sumLoreyHeightByBasalAreaSmall += loreyHeights[sp0Index][ucSmallIndex] * basalAreas[sp0Index][ucSmallIndex]; - sumBasalAreaSmall += basalAreas[sp0Index][ucSmallIndex]; - sumLoreyHeightByBasalAreaAll += loreyHeights[sp0Index][ucAllIndex] * basalAreas[sp0Index][ucAllIndex]; - } - - if (basalAreas[layerIndex][ucAllIndex] > 0.0f) { - quadMeanDiameters[layerIndex][ucAllIndex] = BaseAreaTreeDensityDiameter - .quadMeanDiameter(basalAreas[layerIndex][ucAllIndex], treesPerHectare[layerIndex][ucAllIndex]); - loreyHeights[layerIndex][ucAllIndex] = sumLoreyHeightByBasalAreaAll / basalAreas[layerIndex][ucAllIndex]; - } - - // Calculate the layer's lorey height uc Small value - - if (sumBasalAreaSmall > 0.0f) { - loreyHeights[layerIndex][ucSmallIndex] = sumLoreyHeightByBasalAreaSmall / sumBasalAreaSmall; - } - - // Finally, set the layer's summable UC values (other than All, which was computed above) to - // the sums of those of each of the species. - - for (UtilizationClass uc : UtilizationClass.ALL_CLASSES) { - basalAreas[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues(basalAreas, uc); - treesPerHectare[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues(treesPerHectare, uc); - wholeStemVolumes[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues(wholeStemVolumes, uc); - closeUtilizationVolumes[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues( - closeUtilizationVolumes, uc - ); - cuVolumesMinusDecay[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues(cuVolumesMinusDecay, uc); - cuVolumesMinusDecayAndWastage[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues( - cuVolumesMinusDecayAndWastage, uc - ); - } - } - - private float sumUtilizationClassValues(float[] ucValues, List subjects) { - float sum = 0.0f; - - for (UtilizationClass uc : UtilizationClass.values()) { - if (subjects.contains(uc)) { - sum += ucValues[uc.ordinal()]; - } - } - - return sum; - } - - private float sumSpeciesUtilizationClassValues(float[][] ucValues, UtilizationClass uc) { - float sum = 0.0f; - - for (int sp0Index : this.indices) { - sum += ucValues[sp0Index][uc.ordinal()]; - } - - return sum; - } - - /** - * This method copies the Bank contents out to the VdypLayer instance used to create it and returns that. It is a - * relatively expensive operation and should not be called without due consideration. - * - * @return as described - */ - VdypLayer buildLayerFromBank() { - - transferUtilizationsFromBank(0, layer); - - Collection newSpecies = new ArrayList<>(); - for (int i : indices) { - newSpecies.add(transferSpeciesFromBank(i, layer.getSpecies().get(speciesNames[i]))); - } - layer.setSpecies(newSpecies); - - return layer; - } - - private VdypSpecies transferSpeciesFromBank(int index, VdypSpecies species) { - - VdypSpecies newSpecies = VdypSpecies.build(speciesBuilder -> { - speciesBuilder.copy(species); - speciesBuilder.percentGenus(this.percentagesOfForestedLand[index]); - species.getSite().ifPresentOrElse(site -> speciesBuilder.addSite(VdypSite.build(siteBuilder -> { - siteBuilder.copy(site); - siteBuilder.siteGenus(this.speciesNames[index]); - siteBuilder.ageTotal(Utils.optFloat(ageTotals[index])); - siteBuilder.height(Utils.optFloat(this.dominantHeights[index])); - siteBuilder.siteCurveNumber(Utils.optInt(this.siteCurveNumbers[index])); - siteBuilder.siteIndex(Utils.optFloat(this.siteIndices[index])); - siteBuilder.yearsToBreastHeight(Utils.optFloat(this.yearsToBreastHeight[index])); - })), () -> { - VdypSite site = VdypSite.build(siteBuilder -> { - siteBuilder.polygonIdentifier(species.getPolygonIdentifier()); - siteBuilder.layerType(species.getLayerType()); - siteBuilder.siteGenus(this.speciesNames[index]); - siteBuilder.ageTotal(Utils.optFloat(this.ageTotals[index])); - siteBuilder.height(Utils.optFloat(this.dominantHeights[index])); - siteBuilder.siteCurveNumber(Utils.optInt(this.siteCurveNumbers[index])); - siteBuilder.siteIndex(Utils.optFloat(this.siteIndices[index])); - siteBuilder.yearsToBreastHeight(Utils.optFloat(this.yearsToBreastHeight[index])); - }); - - speciesBuilder.addSite(site); - }); - }); - - transferUtilizationsFromBank(index, newSpecies); - - return newSpecies; - } - - private void transferUtilizationsFromBank(int index, VdypUtilizationHolder uh) { - - for (UtilizationClass uc : UtilizationClass.values()) { - int ucIndex = uc.ordinal(); - uh.getBaseAreaByUtilization().set(uc, basalAreas[index][ucIndex]); - uh.getCloseUtilizationVolumeByUtilization().set(uc, closeUtilizationVolumes[index][ucIndex]); - uh.getCloseUtilizationVolumeNetOfDecayByUtilization().set(uc, cuVolumesMinusDecay[index][ucIndex]); - uh.getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization() - .set(uc, cuVolumesMinusDecayAndWastage[index][ucIndex]); - if (ucIndex < 2 /* only uc 0 and 1 have a lorey height */) { - uh.getLoreyHeightByUtilization().set(uc, loreyHeights[index][ucIndex]); - } - uh.getQuadraticMeanDiameterByUtilization().set(uc, quadMeanDiameters[index][ucIndex]); - uh.getTreesPerHectareByUtilization().set(uc, treesPerHectare[index][ucIndex]); - uh.getWholeStemVolumeByUtilization().set(uc, wholeStemVolumes[index][ucIndex]); - } - } - - public Bank copy() { - return new Bank(this); - } - - private String[] copy(String[] a) { - return Arrays.stream(a).toArray(String[]::new); - } - - private int[] copy(int[] a) { - int[] t = new int[a.length]; - - System.arraycopy(a, 0, t, 0, a.length); - - return t; - } - - private float[] copy(float[] a) { - float[] t = new float[a.length]; - - System.arraycopy(a, 0, t, 0, a.length); - - return t; - } - - private float[][] copy(float[][] a) { - return Arrays.stream(a).map(float[]::clone).toArray(float[][]::new); - } - - private Sp64DistributionSet[] copy(Sp64DistributionSet[] sp64Distributions) { - return Arrays.stream(sp64Distributions).map(s -> s == null ? null : s.copy()) - .toArray(Sp64DistributionSet[]::new); - } -} \ No newline at end of file diff --git a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessingEngine.java b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessingEngine.java index a3425ea5a..2421dc74e 100644 --- a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessingEngine.java +++ b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessingEngine.java @@ -66,6 +66,7 @@ import ca.bc.gov.nrs.vdyp.model.VdypPolygon; import ca.bc.gov.nrs.vdyp.model.VolumeComputeMode; import ca.bc.gov.nrs.vdyp.model.VolumeVariable; +import ca.bc.gov.nrs.vdyp.processing_state.Bank; import ca.bc.gov.nrs.vdyp.si32.site.SiteTool; /** diff --git a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/LayerProcessingState.java b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/LayerProcessingState.java index 60247c49c..9658b589a 100644 --- a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/LayerProcessingState.java +++ b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/LayerProcessingState.java @@ -18,6 +18,7 @@ import ca.bc.gov.nrs.vdyp.model.VdypLayer; import ca.bc.gov.nrs.vdyp.model.VdypSpecies; import ca.bc.gov.nrs.vdyp.model.VolumeVariable; +import ca.bc.gov.nrs.vdyp.processing_state.Bank; class LayerProcessingState { diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/BankTest.java b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/BankTest.java index 5fcdd4226..acff0c791 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/BankTest.java +++ b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/BankTest.java @@ -26,6 +26,7 @@ import ca.bc.gov.nrs.vdyp.model.VdypLayer; import ca.bc.gov.nrs.vdyp.model.VdypSpecies; import ca.bc.gov.nrs.vdyp.model.VdypUtilizationHolder; +import ca.bc.gov.nrs.vdyp.processing_state.Bank; class BankTest { diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/Grow10StoreSpeciesDetails.java b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/Grow10StoreSpeciesDetails.java index 151d71b02..ca2fcaa0d 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/Grow10StoreSpeciesDetails.java +++ b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/Grow10StoreSpeciesDetails.java @@ -22,6 +22,7 @@ import ca.bc.gov.nrs.vdyp.io.parse.value.ValueParseException; import ca.bc.gov.nrs.vdyp.model.PolygonIdentifier; import ca.bc.gov.nrs.vdyp.model.VdypPolygon; +import ca.bc.gov.nrs.vdyp.processing_state.Bank; class Grow10StoreSpeciesDetails { From 03685e93a528fa268a85c3f72fb1578af6f0bac2 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Thu, 17 Oct 2024 13:44:06 -0700 Subject: [PATCH 07/22] Consolidate Bank in common --- .../gov/nrs/vdyp/processing_state/Bank.java | 8 +- .../nrs/vdyp/processing_state}/BankTest.java | 81 +-- .../nrs/vdyp/test/ProcessingTestUtils.java | 182 +++++++ .../java/ca/bc/gov/nrs/vdyp/forward/Bank.java | 465 ------------------ .../vdyp/forward/ForwardProcessingEngine.java | 1 + .../vdyp/forward/LayerProcessingState.java | 1 + .../forward/Grow10StoreSpeciesDetails.java | 1 + .../vdyp/forward/test/ForwardTestUtils.java | 170 ------- 8 files changed, 235 insertions(+), 674 deletions(-) rename lib/{vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward => vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state}/BankTest.java (84%) create mode 100644 lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/test/ProcessingTestUtils.java delete mode 100644 lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/Bank.java diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/Bank.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/Bank.java index 364abf310..781a7a70f 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/Bank.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/processing_state/Bank.java @@ -146,11 +146,11 @@ public int getNSpecies() { return nSpecies; } - int[] getIndices() { + public int[] getIndices() { return indices; } - BecDefinition getBecZone() { + public BecDefinition getBecZone() { return becZone; } @@ -162,7 +162,7 @@ BecDefinition getBecZone() { * @param layer a (presumably modified) version of the layer. * @throws ProcessingException */ - void refreshBank(VdypLayer layer) throws ProcessingException { + public void refreshBank(VdypLayer layer) throws ProcessingException { if (!this.layer.equals(layer)) { throw new IllegalArgumentException( @@ -365,7 +365,7 @@ private float sumSpeciesUtilizationClassValues(float[][] ucValues, UtilizationCl * * @return as described */ - VdypLayer buildLayerFromBank() { + public VdypLayer buildLayerFromBank() { transferUtilizationsFromBank(0, layer); diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/BankTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java similarity index 84% rename from lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/BankTest.java rename to lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java index 5fcdd4226..fa0b44267 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/BankTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java @@ -1,8 +1,6 @@ -package ca.bc.gov.nrs.vdyp.forward; +package ca.bc.gov.nrs.vdyp.processing_state; -import static ca.bc.gov.nrs.vdyp.test.VdypMatchers.controlMapHasEntry; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -10,44 +8,73 @@ import java.util.List; import java.util.Map; -import org.hamcrest.Matcher; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import ca.bc.gov.nrs.vdyp.application.ProcessingException; -import ca.bc.gov.nrs.vdyp.common.ControlKey; -import ca.bc.gov.nrs.vdyp.forward.test.ForwardTestUtils; +import ca.bc.gov.nrs.vdyp.common.Utils; import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; -import ca.bc.gov.nrs.vdyp.model.GenusDefinitionMap; import ca.bc.gov.nrs.vdyp.model.LayerType; import ca.bc.gov.nrs.vdyp.model.UtilizationClass; import ca.bc.gov.nrs.vdyp.model.UtilizationVector; import ca.bc.gov.nrs.vdyp.model.VdypEntity; import ca.bc.gov.nrs.vdyp.model.VdypLayer; +import ca.bc.gov.nrs.vdyp.model.VdypPolygon; import ca.bc.gov.nrs.vdyp.model.VdypSpecies; import ca.bc.gov.nrs.vdyp.model.VdypUtilizationHolder; +import ca.bc.gov.nrs.vdyp.test.ProcessingTestUtils; +import ca.bc.gov.nrs.vdyp.test.TestUtils; class BankTest { - private ForwardControlParser parser; private Map controlMap; + private VdypPolygon polygon; - @SuppressWarnings({ "unchecked", "rawtypes" }) @BeforeEach void before() throws IOException, ResourceParseException { - parser = new ForwardControlParser(); - controlMap = ForwardTestUtils.parse(parser, "VDYP.CTR"); - assertThat(controlMap, (Matcher) controlMapHasEntry(ControlKey.SP0_DEF, instanceOf(GenusDefinitionMap.class))); + controlMap = TestUtils.loadControlMap(); + + var bec = Utils.getBec("CDF", controlMap); + + polygon = VdypPolygon.build(pb -> { + pb.polygonIdentifier("Test", 2024); + + pb.percentAvailable(99f); + pb.biogeoclimaticZone(bec); + pb.forestInventoryZone("A"); + + pb.addLayer(lb -> { + lb.layerType(LayerType.PRIMARY); + + lb.addSpecies(sb -> { + sb.genus("B", controlMap); + sb.baseArea(0.4f); + }); + lb.addSpecies(sb -> { + sb.genus("C", controlMap); + sb.baseArea(0.6f); + }); + lb.addSpecies(sb -> { + sb.genus("D", controlMap); + sb.baseArea(10f); + }); + lb.addSpecies(sb -> { + sb.genus("H", controlMap); + sb.baseArea(50f); + }); + lb.addSpecies(sb -> { + sb.genus("S", controlMap); + sb.baseArea(99.9f); + }); + }); + }); + } @Test void testConstruction() throws IOException, ResourceParseException, ProcessingException { - ForwardDataStreamReader reader = new ForwardDataStreamReader(controlMap); - - var polygon = reader.readNextPolygon().orElseThrow(() -> new AssertionError("No polygons defined")); - VdypLayer pLayer = polygon.getLayers().get(LayerType.PRIMARY); assertThat(pLayer, notNullValue()); @@ -105,16 +132,12 @@ void testConstruction() throws IOException, ResourceParseException, ProcessingEx @Test void testSetCopy() throws IOException, ResourceParseException, ProcessingException { - ForwardDataStreamReader reader = new ForwardDataStreamReader(controlMap); - - var polygon = reader.readNextPolygon().orElseThrow(() -> new AssertionError("No polygons defined")); - VdypLayer pLayer = polygon.getLayers().get(LayerType.PRIMARY); assertThat(pLayer, notNullValue()); Bank bank = new Bank(pLayer, polygon.getBiogeoclimaticZone(), s -> true); - pLayer = ForwardTestUtils.normalizeLayer(pLayer); + pLayer = ProcessingTestUtils.normalizeLayer(pLayer); verifyBankMatchesLayer(bank, pLayer); Bank ppsCopy = bank.copy(); @@ -125,10 +148,6 @@ void testSetCopy() throws IOException, ResourceParseException, ProcessingExcepti @Test void testRemoveSmallLayers() throws IOException, ResourceParseException, ProcessingException { - ForwardDataStreamReader reader = new ForwardDataStreamReader(controlMap); - - var polygon = reader.readNextPolygon().orElseThrow(() -> new AssertionError("No polygons defined")); - VdypLayer pLayer = polygon.getLayers().get(LayerType.PRIMARY); assertThat(pLayer, notNullValue()); @@ -154,10 +173,6 @@ void testRemoveSmallLayers() throws IOException, ResourceParseException, Process @Test void testCopyConstructor() throws IOException, ResourceParseException, ProcessingException { - ForwardDataStreamReader reader = new ForwardDataStreamReader(controlMap); - - var polygon = reader.readNextPolygon().orElseThrow(() -> new AssertionError("No polygons defined")); - VdypLayer pLayer = polygon.getLayers().get(LayerType.PRIMARY); assertThat(pLayer, notNullValue()); @@ -165,23 +180,19 @@ void testCopyConstructor() throws IOException, ResourceParseException, Processin Bank bankCopy = new Bank(bank); - pLayer = ForwardTestUtils.normalizeLayer(pLayer); + pLayer = ProcessingTestUtils.normalizeLayer(pLayer); verifyBankMatchesLayer(bankCopy, pLayer); } @Test void testLayerUpdate() throws IOException, ResourceParseException, ProcessingException { - ForwardDataStreamReader reader = new ForwardDataStreamReader(controlMap); - - var polygon = reader.readNextPolygon().orElseThrow(() -> new AssertionError("No polygons defined")); - VdypLayer pLayer = polygon.getLayers().get(LayerType.PRIMARY); assertThat(pLayer, notNullValue()); Bank bank = new Bank(pLayer, polygon.getBiogeoclimaticZone(), s -> true); - pLayer = ForwardTestUtils.normalizeLayer(pLayer); + pLayer = ProcessingTestUtils.normalizeLayer(pLayer); verifyBankMatchesLayer(bank, pLayer); diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/test/ProcessingTestUtils.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/test/ProcessingTestUtils.java new file mode 100644 index 000000000..568ffdefa --- /dev/null +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/test/ProcessingTestUtils.java @@ -0,0 +1,182 @@ +package ca.bc.gov.nrs.vdyp.test; + +import java.util.List; + +import ca.bc.gov.nrs.vdyp.common_calculators.BaseAreaTreeDensityDiameter; +import ca.bc.gov.nrs.vdyp.model.UtilizationClass; +import ca.bc.gov.nrs.vdyp.model.UtilizationVector; +import ca.bc.gov.nrs.vdyp.model.VdypLayer; +import ca.bc.gov.nrs.vdyp.model.VdypSpecies; + +public class ProcessingTestUtils { + public static VdypLayer normalizeLayer(VdypLayer layer) { + + // Set uc All to the sum of the UC values, UC 7.5 and above only, for the summable + // values, and calculate quad-mean-diameter from these values. + + UtilizationClass ucAll = UtilizationClass.ALL; + UtilizationClass ucSmall = UtilizationClass.SMALL; + + for (var species : layer.getSpecies().values()) { + + species.getBaseAreaByUtilization().set( + ucAll, sumUtilizationClassValues(species.getBaseAreaByUtilization(), UtilizationClass.UTIL_CLASSES) + ); + species.getTreesPerHectareByUtilization().set( + ucAll, + sumUtilizationClassValues(species.getTreesPerHectareByUtilization(), UtilizationClass.UTIL_CLASSES) + ); + species.getWholeStemVolumeByUtilization().set( + ucAll, + sumUtilizationClassValues(species.getWholeStemVolumeByUtilization(), UtilizationClass.UTIL_CLASSES) + ); + species.getCloseUtilizationVolumeByUtilization().set( + ucAll, + sumUtilizationClassValues( + species.getCloseUtilizationVolumeByUtilization(), UtilizationClass.UTIL_CLASSES + ) + ); + species.getCloseUtilizationVolumeNetOfDecayByUtilization().set( + ucAll, + sumUtilizationClassValues( + species.getCloseUtilizationVolumeNetOfDecayByUtilization(), UtilizationClass.UTIL_CLASSES + ) + ); + species.getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization().set( + ucAll, + sumUtilizationClassValues( + species.getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization(), + UtilizationClass.UTIL_CLASSES + ) + ); + + if (species.getBaseAreaByUtilization().get(ucAll) > 0.0f) { + species.getQuadraticMeanDiameterByUtilization().set( + ucAll, + BaseAreaTreeDensityDiameter.quadMeanDiameter( + species.getBaseAreaByUtilization().get(ucAll), + species.getTreesPerHectareByUtilization().get(ucAll) + ) + ); + } + } + + // Set the layer's uc All values (for summable types) to the sum of those of the + // individual species. + + layer.getBaseAreaByUtilization() + .set(ucAll, sumSpeciesUtilizationClassValues(layer, "BaseArea", UtilizationClass.ALL)); + layer.getTreesPerHectareByUtilization() + .set(ucAll, sumSpeciesUtilizationClassValues(layer, "TreesPerHectare", UtilizationClass.ALL)); + layer.getWholeStemVolumeByUtilization() + .set(ucAll, sumSpeciesUtilizationClassValues(layer, "WholeStemVolume", UtilizationClass.ALL)); + layer.getCloseUtilizationVolumeByUtilization() + .set(ucAll, sumSpeciesUtilizationClassValues(layer, "CloseUtilizationVolume", UtilizationClass.ALL)); + layer.getCloseUtilizationVolumeNetOfDecayByUtilization().set( + ucAll, sumSpeciesUtilizationClassValues(layer, "CloseUtilizationVolumeNetOfDecay", UtilizationClass.ALL) + ); + layer.getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization().set( + ucAll, + sumSpeciesUtilizationClassValues( + layer, "CloseUtilizationVolumeNetOfDecayAndWaste", UtilizationClass.ALL + ) + ); + + // Calculate the layer's uc All values for quad-mean-diameter and lorey height + + float sumLoreyHeightByBasalAreaSmall = 0.0f; + float sumBasalAreaSmall = 0.0f; + float sumLoreyHeightByBasalAreaAll = 0.0f; + + for (var species : layer.getSpecies().values()) { + sumLoreyHeightByBasalAreaSmall += species.getLoreyHeightByUtilization().get(ucSmall) + * species.getBaseAreaByUtilization().get(ucSmall); + sumBasalAreaSmall += species.getBaseAreaByUtilization().get(ucSmall); + sumLoreyHeightByBasalAreaAll += species.getLoreyHeightByUtilization().get(ucAll) + * species.getBaseAreaByUtilization().get(ucAll); + } + + if (layer.getBaseAreaByUtilization().get(ucAll) > 0.0f) { + layer.getQuadraticMeanDiameterByUtilization().set( + ucAll, + BaseAreaTreeDensityDiameter.quadMeanDiameter( + layer.getBaseAreaByUtilization().get(ucAll), + layer.getTreesPerHectareByUtilization().get(ucAll) + ) + ); + layer.getLoreyHeightByUtilization() + .set(ucAll, sumLoreyHeightByBasalAreaAll / layer.getBaseAreaByUtilization().get(ucAll)); + } + + // Calculate the layer's lorey height uc Small value + + if (sumBasalAreaSmall > 0.0f) { + layer.getLoreyHeightByUtilization().set(ucSmall, sumLoreyHeightByBasalAreaSmall / sumBasalAreaSmall); + } + + // Finally, set the layer's summable UC values (other than All, which was computed above) to + // the sums of those of each of the species. + + for (UtilizationClass uc : UtilizationClass.ALL_CLASSES) { + layer.getBaseAreaByUtilization().set(uc, sumSpeciesUtilizationClassValues(layer, "BaseArea", uc)); + layer.getTreesPerHectareByUtilization() + .set(uc, sumSpeciesUtilizationClassValues(layer, "TreesPerHectare", uc)); + layer.getWholeStemVolumeByUtilization() + .set(uc, sumSpeciesUtilizationClassValues(layer, "WholeStemVolume", uc)); + layer.getCloseUtilizationVolumeByUtilization() + .set(uc, sumSpeciesUtilizationClassValues(layer, "CloseUtilizationVolume", uc)); + layer.getCloseUtilizationVolumeNetOfDecayByUtilization() + .set(uc, sumSpeciesUtilizationClassValues(layer, "CloseUtilizationVolumeNetOfDecay", uc)); + layer.getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization() + .set(uc, sumSpeciesUtilizationClassValues(layer, "CloseUtilizationVolumeNetOfDecayAndWaste", uc)); + } + + return layer; + } + + private static float sumUtilizationClassValues(UtilizationVector ucValues, List subjects) { + float sum = 0.0f; + + for (UtilizationClass uc : UtilizationClass.values()) { + if (subjects.contains(uc)) { + sum += ucValues.get(uc); + } + } + + return sum; + } + + private static float sumSpeciesUtilizationClassValues(VdypLayer layer, String uvName, UtilizationClass uc) { + float sum = 0.0f; + + for (VdypSpecies species : layer.getSpecies().values()) { + switch (uvName) { + case "CloseUtilizationVolumeNetOfDecayWasteAndBreakage": + sum += species.getCloseUtilizationVolumeNetOfDecayWasteAndBreakageByUtilization().get(uc); + break; + case "CloseUtilizationVolumeNetOfDecayAndWaste": + sum += species.getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization().get(uc); + break; + case "CloseUtilizationVolumeNetOfDecay": + sum += species.getCloseUtilizationVolumeNetOfDecayByUtilization().get(uc); + break; + case "CloseUtilizationVolume": + sum += species.getCloseUtilizationVolumeByUtilization().get(uc); + break; + case "WholeStemVolume": + sum += species.getWholeStemVolumeByUtilization().get(uc); + break; + case "TreesPerHectare": + sum += species.getTreesPerHectareByUtilization().get(uc); + break; + case "BaseArea": + sum += species.getBaseAreaByUtilization().get(uc); + break; + default: + throw new IllegalStateException(uvName + " is not a known utilization vector name"); + } + } + + return sum; + } +} diff --git a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/Bank.java b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/Bank.java deleted file mode 100644 index 7728c2a4f..000000000 --- a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/Bank.java +++ /dev/null @@ -1,465 +0,0 @@ -package ca.bc.gov.nrs.vdyp.forward; - -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.IntStream; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ca.bc.gov.nrs.vdyp.application.ProcessingException; -import ca.bc.gov.nrs.vdyp.common.Utils; -import ca.bc.gov.nrs.vdyp.common_calculators.BaseAreaTreeDensityDiameter; -import ca.bc.gov.nrs.vdyp.model.BecDefinition; -import ca.bc.gov.nrs.vdyp.model.Sp64DistributionSet; -import ca.bc.gov.nrs.vdyp.model.UtilizationClass; -import ca.bc.gov.nrs.vdyp.model.VdypEntity; -import ca.bc.gov.nrs.vdyp.model.VdypLayer; -import ca.bc.gov.nrs.vdyp.model.VdypSite; -import ca.bc.gov.nrs.vdyp.model.VdypSpecies; -import ca.bc.gov.nrs.vdyp.model.VdypUtilizationHolder; - -class Bank { - - @SuppressWarnings("unused") - private static final Logger logger = LoggerFactory.getLogger(Bank.class); - - private static final int N_UTILIZATION_CLASSES = UtilizationClass.values().length; - - private final VdypLayer layer; - private final BecDefinition becZone; - - /** - * The number of species in the state. Note that all arrays have this value plus one elements in them; the element - * at index 0 is unused for the species values* and contains the default utilization in the Utilization values. - * - * (*) except: siteCurveNumbers[0] is used to store the site curve of the primary species. - */ - private int nSpecies; // BANK1 NSPB - private int[] indices; - - // Species information - - public final String[/* nSpecies + 1 */] speciesNames; // BANK2 SP0B - public final Sp64DistributionSet[/* nSpecies + 1 */] sp64Distributions; // BANK2 SP64DISTB - public final float[/* nSpecies + 1 */] siteIndices; // BANK3 SIB - public final float[/* nSpecies + 1 */] dominantHeights; // BANK3 HDB - public final float[/* nSpecies + 1 */] ageTotals; // BANK3 AGETOTB - public final float[/* nSpecies + 1 */] yearsAtBreastHeight; // BANK3 AGEBHB - public final float[/* nSpecies + 1 */] yearsToBreastHeight; // BANK3 YTBHB - public final int[/* nSpecies + 1 */] siteCurveNumbers; // BANK3 SCNB - public final int[/* nSpecies + 1 */] speciesIndices; // BANK1 ISPB - public final float[/* nSpecies + 1 */] percentagesOfForestedLand; // BANK1 PCTB - - // Utilization information, per Species - - public final float[/* nSpecies + 1, including 0 */][/* all ucs */] basalAreas; // BANK1 BAB. Units: m^2/hectare - public final float[/* nSpecies + 1, including 0 */][/* all ucs */] closeUtilizationVolumes; // BANK1 VOLCUB - public final float[/* nSpecies + 1, including 0 */][/* all ucs */] cuVolumesMinusDecay; // BANK1 VOL_DB - public final float[/* nSpecies + 1, including 0 */][/* all ucs */] cuVolumesMinusDecayAndWastage; // BANK1 VOL_DW_B - public final float[/* nSpecies + 1, including 0 */][/* uc -1 and 0 only */] loreyHeights; // BANK1 HLB - public final float[/* nSpecies + 1, including 0 */][/* all ucs */] quadMeanDiameters; // BANK1 DQB - public final float[/* nSpecies + 1, including 0 */][/* all ucs */] treesPerHectare; // BANK1 TPHB - public final float[/* nSpecies + 1, including 0 */][/* all ucs */] wholeStemVolumes; // BANK1 VOLWSB - - public Bank(VdypLayer layer, BecDefinition becZone, Predicate retainCriteria) { - - this.layer = layer; - this.becZone = becZone; - - List speciesToRetain = layer.getSpecies().values().stream().filter(s -> retainCriteria.test(s)) - .sorted((s1, s2) -> s1.getGenusIndex() - s2.getGenusIndex()).toList(); - - this.nSpecies = speciesToRetain.size(); - this.indices = IntStream.range(1, nSpecies + 1).toArray(); - - // In the following, index 0 is unused - speciesNames = new String[nSpecies + 1]; - sp64Distributions = new Sp64DistributionSet[getNSpecies() + 1]; - siteIndices = new float[nSpecies + 1]; - dominantHeights = new float[nSpecies + 1]; - ageTotals = new float[nSpecies + 1]; - yearsAtBreastHeight = new float[nSpecies + 1]; - yearsToBreastHeight = new float[nSpecies + 1]; - siteCurveNumbers = new int[nSpecies + 1]; - speciesIndices = new int[nSpecies + 1]; - percentagesOfForestedLand = new float[nSpecies + 1]; - - // In the following, index 0 is used for the default species utilization - basalAreas = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; - closeUtilizationVolumes = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; - cuVolumesMinusDecay = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; - cuVolumesMinusDecayAndWastage = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; - loreyHeights = new float[nSpecies + 1][2]; - quadMeanDiameters = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; - treesPerHectare = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; - wholeStemVolumes = new float[nSpecies + 1][N_UTILIZATION_CLASSES]; - - int nextSlot = 1; - for (VdypSpecies s : speciesToRetain) { - transferSpeciesIntoBank(nextSlot++, s); - } - - transferUtilizationSetIntoBank(0, layer); - - // BANKCHK1 - calculate UC All values from components (rather than rely on the values - // provided in the input.) - - setCalculateUtilizationClassAllValues(); - } - - public Bank(Bank source) { - - this.becZone = source.becZone; - this.layer = source.layer; - - this.nSpecies = source.nSpecies; - this.indices = copy(source.indices); - this.speciesNames = copy(source.speciesNames); - this.speciesIndices = copy(source.speciesIndices); - - this.siteCurveNumbers = copy(source.siteCurveNumbers); - this.sp64Distributions = copy(source.sp64Distributions); - - this.ageTotals = copy(source.ageTotals); - this.dominantHeights = copy(source.dominantHeights); - this.percentagesOfForestedLand = copy(source.percentagesOfForestedLand); - this.siteIndices = copy(source.siteIndices); - this.yearsAtBreastHeight = copy(source.yearsAtBreastHeight); - this.yearsToBreastHeight = copy(source.yearsToBreastHeight); - - this.basalAreas = copy(source.basalAreas); - this.closeUtilizationVolumes = copy(source.closeUtilizationVolumes); - this.cuVolumesMinusDecay = copy(source.cuVolumesMinusDecay); - this.cuVolumesMinusDecayAndWastage = copy(source.cuVolumesMinusDecayAndWastage); - this.loreyHeights = copy(source.loreyHeights); - this.quadMeanDiameters = copy(source.quadMeanDiameters); - this.treesPerHectare = copy(source.treesPerHectare); - this.wholeStemVolumes = copy(source.wholeStemVolumes); - } - - public int getNSpecies() { - return nSpecies; - } - - int[] getIndices() { - return indices; - } - - BecDefinition getBecZone() { - return becZone; - } - - /** - * Refresh the values in the bank with an updated version (given) of the layer used to create the bank. The - * modifications cannot include any changes to the set of species, although the details of those species may of - * course change. - * - * @param layer a (presumably modified) version of the layer. - * @throws ProcessingException - */ - void refreshBank(VdypLayer layer) throws ProcessingException { - - if (!this.layer.equals(layer)) { - throw new IllegalArgumentException( - MessageFormat.format( - "One cannot refresh a bank from a" - + " layer ({0}) different from the one used to create the bank ({1})", - this.layer, layer - ) - ); - } - - List species = layer.getSpecies().values().stream() - .sorted((s1, s2) -> s1.getGenusIndex() - s2.getGenusIndex()).toList(); - - transferUtilizationSetIntoBank(0, layer); - - int nextSlot = 1; - for (VdypSpecies s : species) { - transferSpeciesIntoBank(nextSlot++, s); - } - } - - private void transferSpeciesIntoBank(int index, VdypSpecies species) { - - speciesNames[index] = species.getGenus(); - sp64Distributions[index] = species.getSp64DistributionSet(); - speciesIndices[index] = species.getGenusIndex(); - - species.getSite().ifPresentOrElse(s -> { - siteIndices[index] = s.getSiteIndex().orElse(VdypEntity.MISSING_FLOAT_VALUE); - dominantHeights[index] = s.getHeight().orElse(VdypEntity.MISSING_FLOAT_VALUE); - ageTotals[index] = s.getAgeTotal().orElse(VdypEntity.MISSING_FLOAT_VALUE); - yearsToBreastHeight[index] = s.getYearsToBreastHeight().orElse(VdypEntity.MISSING_FLOAT_VALUE); - if (ageTotals[index] != VdypEntity.MISSING_FLOAT_VALUE - && yearsToBreastHeight[index] != VdypEntity.MISSING_FLOAT_VALUE) { - yearsAtBreastHeight[index] = ageTotals[index] - yearsToBreastHeight[index]; - } else { - yearsAtBreastHeight[index] = VdypEntity.MISSING_FLOAT_VALUE; - } - siteCurveNumbers[index] = s.getSiteCurveNumber().orElse(VdypEntity.MISSING_INTEGER_VALUE); - // percentForestedLand is output-only and so not assigned here. - }, () -> { - siteIndices[index] = VdypEntity.MISSING_FLOAT_VALUE; - dominantHeights[index] = VdypEntity.MISSING_FLOAT_VALUE; - ageTotals[index] = VdypEntity.MISSING_FLOAT_VALUE; - yearsToBreastHeight[index] = VdypEntity.MISSING_FLOAT_VALUE; - yearsAtBreastHeight[index] = VdypEntity.MISSING_FLOAT_VALUE; - siteCurveNumbers[index] = VdypEntity.MISSING_INTEGER_VALUE; - }); - - transferUtilizationSetIntoBank(index, species); - } - - private void transferUtilizationSetIntoBank(int index, VdypUtilizationHolder uh) { - - for (UtilizationClass uc : UtilizationClass.values()) { - int ucIndex = uc.ordinal(); - basalAreas[index][ucIndex] = uh.getBaseAreaByUtilization().get(uc); - closeUtilizationVolumes[index][ucIndex] = uh.getCloseUtilizationVolumeByUtilization().get(uc); - cuVolumesMinusDecay[index][ucIndex] = uh.getCloseUtilizationVolumeNetOfDecayByUtilization().get(uc); - cuVolumesMinusDecayAndWastage[index][ucIndex] = uh - .getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization().get(uc); - if (ucIndex < 2 /* only uc 0 and 1 have a lorey height */) { - loreyHeights[index][ucIndex] = uh.getLoreyHeightByUtilization().get(uc); - } - quadMeanDiameters[index][ucIndex] = uh.getQuadraticMeanDiameterByUtilization().get(uc); - treesPerHectare[index][ucIndex] = uh.getTreesPerHectareByUtilization().get(uc); - wholeStemVolumes[index][ucIndex] = uh.getWholeStemVolumeByUtilization().get(uc); - } - } - - /** - * For each species, set uc All to the sum of the UC values, UC 7.5 and above only, for the summable values, and - * calculate quad-mean-diameter from these values. - *

- * For the layer, set uc All values (for summable types) to the sum of those of the individual species and set the - * other uc values to the sum of those of the individual species. Calculate the uc All value for quad-mean-diameter, - * and the uc All and Small value for lorey-height. - */ - private void setCalculateUtilizationClassAllValues() { - - int layerIndex = 0; - int ucAllIndex = UtilizationClass.ALL.ordinal(); - int ucSmallIndex = UtilizationClass.SMALL.ordinal(); - - // Each species - - for (int sp0Index : indices) { - - basalAreas[sp0Index][ucAllIndex] = sumUtilizationClassValues( - basalAreas[sp0Index], UtilizationClass.UTIL_CLASSES - ); - treesPerHectare[sp0Index][ucAllIndex] = sumUtilizationClassValues( - treesPerHectare[sp0Index], UtilizationClass.UTIL_CLASSES - ); - wholeStemVolumes[sp0Index][ucAllIndex] = sumUtilizationClassValues( - wholeStemVolumes[sp0Index], UtilizationClass.UTIL_CLASSES - ); - closeUtilizationVolumes[sp0Index][ucAllIndex] = sumUtilizationClassValues( - closeUtilizationVolumes[sp0Index], UtilizationClass.UTIL_CLASSES - ); - cuVolumesMinusDecay[sp0Index][ucAllIndex] = sumUtilizationClassValues( - cuVolumesMinusDecay[sp0Index], UtilizationClass.UTIL_CLASSES - ); - cuVolumesMinusDecayAndWastage[sp0Index][ucAllIndex] = sumUtilizationClassValues( - cuVolumesMinusDecayAndWastage[sp0Index], UtilizationClass.UTIL_CLASSES - ); - - if (basalAreas[sp0Index][ucAllIndex] > 0.0f) { - quadMeanDiameters[sp0Index][ucAllIndex] = BaseAreaTreeDensityDiameter - .quadMeanDiameter(basalAreas[sp0Index][ucAllIndex], treesPerHectare[sp0Index][ucAllIndex]); - } - } - - // Layer - - basalAreas[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues(basalAreas, UtilizationClass.ALL); - treesPerHectare[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues( - treesPerHectare, UtilizationClass.ALL - ); - wholeStemVolumes[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues( - wholeStemVolumes, UtilizationClass.ALL - ); - closeUtilizationVolumes[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues( - closeUtilizationVolumes, UtilizationClass.ALL - ); - cuVolumesMinusDecay[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues( - cuVolumesMinusDecay, UtilizationClass.ALL - ); - cuVolumesMinusDecayAndWastage[layerIndex][ucAllIndex] = sumSpeciesUtilizationClassValues( - cuVolumesMinusDecayAndWastage, UtilizationClass.ALL - ); - - // Calculate the layer's uc All values for quad-mean-diameter and lorey height - - float sumLoreyHeightByBasalAreaSmall = 0.0f; - float sumBasalAreaSmall = 0.0f; - float sumLoreyHeightByBasalAreaAll = 0.0f; - - for (int sp0Index : indices) { - sumLoreyHeightByBasalAreaSmall += loreyHeights[sp0Index][ucSmallIndex] * basalAreas[sp0Index][ucSmallIndex]; - sumBasalAreaSmall += basalAreas[sp0Index][ucSmallIndex]; - sumLoreyHeightByBasalAreaAll += loreyHeights[sp0Index][ucAllIndex] * basalAreas[sp0Index][ucAllIndex]; - } - - if (basalAreas[layerIndex][ucAllIndex] > 0.0f) { - quadMeanDiameters[layerIndex][ucAllIndex] = BaseAreaTreeDensityDiameter - .quadMeanDiameter(basalAreas[layerIndex][ucAllIndex], treesPerHectare[layerIndex][ucAllIndex]); - loreyHeights[layerIndex][ucAllIndex] = sumLoreyHeightByBasalAreaAll / basalAreas[layerIndex][ucAllIndex]; - } - - // Calculate the layer's lorey height uc Small value - - if (sumBasalAreaSmall > 0.0f) { - loreyHeights[layerIndex][ucSmallIndex] = sumLoreyHeightByBasalAreaSmall / sumBasalAreaSmall; - } - - // Finally, set the layer's summable UC values (other than All, which was computed above) to - // the sums of those of each of the species. - - for (UtilizationClass uc : UtilizationClass.ALL_CLASSES) { - basalAreas[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues(basalAreas, uc); - treesPerHectare[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues(treesPerHectare, uc); - wholeStemVolumes[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues(wholeStemVolumes, uc); - closeUtilizationVolumes[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues( - closeUtilizationVolumes, uc - ); - cuVolumesMinusDecay[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues(cuVolumesMinusDecay, uc); - cuVolumesMinusDecayAndWastage[layerIndex][uc.ordinal()] = sumSpeciesUtilizationClassValues( - cuVolumesMinusDecayAndWastage, uc - ); - } - } - - private float sumUtilizationClassValues(float[] ucValues, List subjects) { - float sum = 0.0f; - - for (UtilizationClass uc : UtilizationClass.values()) { - if (subjects.contains(uc)) { - sum += ucValues[uc.ordinal()]; - } - } - - return sum; - } - - private float sumSpeciesUtilizationClassValues(float[][] ucValues, UtilizationClass uc) { - float sum = 0.0f; - - for (int sp0Index : this.indices) { - sum += ucValues[sp0Index][uc.ordinal()]; - } - - return sum; - } - - /** - * This method copies the Bank contents out to the VdypLayer instance used to create it and returns that. It is a - * relatively expensive operation and should not be called without due consideration. - * - * @return as described - */ - VdypLayer buildLayerFromBank() { - - transferUtilizationsFromBank(0, layer); - - Collection newSpecies = new ArrayList<>(); - for (int i : indices) { - newSpecies.add(transferSpeciesFromBank(i, layer.getSpecies().get(speciesNames[i]))); - } - layer.setSpecies(newSpecies); - - return layer; - } - - private VdypSpecies transferSpeciesFromBank(int index, VdypSpecies species) { - - VdypSpecies newSpecies = VdypSpecies.build(speciesBuilder -> { - speciesBuilder.copy(species); - speciesBuilder.percentGenus(this.percentagesOfForestedLand[index]); - species.getSite().ifPresentOrElse(site -> speciesBuilder.addSite(VdypSite.build(siteBuilder -> { - siteBuilder.copy(site); - siteBuilder.siteGenus(this.speciesNames[index]); - siteBuilder.ageTotal(Utils.optFloat(ageTotals[index])); - siteBuilder.height(Utils.optFloat(this.dominantHeights[index])); - siteBuilder.siteCurveNumber(Utils.optInt(this.siteCurveNumbers[index])); - siteBuilder.siteIndex(Utils.optFloat(this.siteIndices[index])); - siteBuilder.yearsToBreastHeight(Utils.optFloat(this.yearsToBreastHeight[index])); - })), () -> { - VdypSite site = VdypSite.build(siteBuilder -> { - siteBuilder.polygonIdentifier(species.getPolygonIdentifier()); - siteBuilder.layerType(species.getLayerType()); - siteBuilder.siteGenus(this.speciesNames[index]); - siteBuilder.ageTotal(Utils.optFloat(this.ageTotals[index])); - siteBuilder.height(Utils.optFloat(this.dominantHeights[index])); - siteBuilder.siteCurveNumber(Utils.optInt(this.siteCurveNumbers[index])); - siteBuilder.siteIndex(Utils.optFloat(this.siteIndices[index])); - siteBuilder.yearsToBreastHeight(Utils.optFloat(this.yearsToBreastHeight[index])); - }); - - speciesBuilder.addSite(site); - }); - }); - - transferUtilizationsFromBank(index, newSpecies); - - return newSpecies; - } - - private void transferUtilizationsFromBank(int index, VdypUtilizationHolder uh) { - - for (UtilizationClass uc : UtilizationClass.values()) { - int ucIndex = uc.ordinal(); - uh.getBaseAreaByUtilization().set(uc, basalAreas[index][ucIndex]); - uh.getCloseUtilizationVolumeByUtilization().set(uc, closeUtilizationVolumes[index][ucIndex]); - uh.getCloseUtilizationVolumeNetOfDecayByUtilization().set(uc, cuVolumesMinusDecay[index][ucIndex]); - uh.getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization() - .set(uc, cuVolumesMinusDecayAndWastage[index][ucIndex]); - if (ucIndex < 2 /* only uc 0 and 1 have a lorey height */) { - uh.getLoreyHeightByUtilization().set(uc, loreyHeights[index][ucIndex]); - } - uh.getQuadraticMeanDiameterByUtilization().set(uc, quadMeanDiameters[index][ucIndex]); - uh.getTreesPerHectareByUtilization().set(uc, treesPerHectare[index][ucIndex]); - uh.getWholeStemVolumeByUtilization().set(uc, wholeStemVolumes[index][ucIndex]); - } - } - - public Bank copy() { - return new Bank(this); - } - - private String[] copy(String[] a) { - return Arrays.stream(a).toArray(String[]::new); - } - - private int[] copy(int[] a) { - int[] t = new int[a.length]; - - System.arraycopy(a, 0, t, 0, a.length); - - return t; - } - - private float[] copy(float[] a) { - float[] t = new float[a.length]; - - System.arraycopy(a, 0, t, 0, a.length); - - return t; - } - - private float[][] copy(float[][] a) { - return Arrays.stream(a).map(float[]::clone).toArray(float[][]::new); - } - - private Sp64DistributionSet[] copy(Sp64DistributionSet[] sp64Distributions) { - return Arrays.stream(sp64Distributions).map(s -> s == null ? null : s.copy()) - .toArray(Sp64DistributionSet[]::new); - } -} \ No newline at end of file diff --git a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessingEngine.java b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessingEngine.java index a3425ea5a..2421dc74e 100644 --- a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessingEngine.java +++ b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessingEngine.java @@ -66,6 +66,7 @@ import ca.bc.gov.nrs.vdyp.model.VdypPolygon; import ca.bc.gov.nrs.vdyp.model.VolumeComputeMode; import ca.bc.gov.nrs.vdyp.model.VolumeVariable; +import ca.bc.gov.nrs.vdyp.processing_state.Bank; import ca.bc.gov.nrs.vdyp.si32.site.SiteTool; /** diff --git a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/LayerProcessingState.java b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/LayerProcessingState.java index 60247c49c..9658b589a 100644 --- a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/LayerProcessingState.java +++ b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/LayerProcessingState.java @@ -18,6 +18,7 @@ import ca.bc.gov.nrs.vdyp.model.VdypLayer; import ca.bc.gov.nrs.vdyp.model.VdypSpecies; import ca.bc.gov.nrs.vdyp.model.VolumeVariable; +import ca.bc.gov.nrs.vdyp.processing_state.Bank; class LayerProcessingState { diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/Grow10StoreSpeciesDetails.java b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/Grow10StoreSpeciesDetails.java index 151d71b02..ca2fcaa0d 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/Grow10StoreSpeciesDetails.java +++ b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/Grow10StoreSpeciesDetails.java @@ -22,6 +22,7 @@ import ca.bc.gov.nrs.vdyp.io.parse.value.ValueParseException; import ca.bc.gov.nrs.vdyp.model.PolygonIdentifier; import ca.bc.gov.nrs.vdyp.model.VdypPolygon; +import ca.bc.gov.nrs.vdyp.processing_state.Bank; class Grow10StoreSpeciesDetails { diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/test/ForwardTestUtils.java b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/test/ForwardTestUtils.java index 8f3e2b11b..e02234555 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/test/ForwardTestUtils.java +++ b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/test/ForwardTestUtils.java @@ -118,174 +118,4 @@ public static void setGrowthTargetYear(ForwardProcessingState fps, int targetYea } } - public static VdypLayer normalizeLayer(VdypLayer layer) { - - // Set uc All to the sum of the UC values, UC 7.5 and above only, for the summable - // values, and calculate quad-mean-diameter from these values. - - UtilizationClass ucAll = UtilizationClass.ALL; - UtilizationClass ucSmall = UtilizationClass.SMALL; - - for (var species : layer.getSpecies().values()) { - - species.getBaseAreaByUtilization().set( - ucAll, sumUtilizationClassValues(species.getBaseAreaByUtilization(), UtilizationClass.UTIL_CLASSES) - ); - species.getTreesPerHectareByUtilization().set( - ucAll, - sumUtilizationClassValues(species.getTreesPerHectareByUtilization(), UtilizationClass.UTIL_CLASSES) - ); - species.getWholeStemVolumeByUtilization().set( - ucAll, - sumUtilizationClassValues(species.getWholeStemVolumeByUtilization(), UtilizationClass.UTIL_CLASSES) - ); - species.getCloseUtilizationVolumeByUtilization().set( - ucAll, - sumUtilizationClassValues( - species.getCloseUtilizationVolumeByUtilization(), UtilizationClass.UTIL_CLASSES - ) - ); - species.getCloseUtilizationVolumeNetOfDecayByUtilization().set( - ucAll, - sumUtilizationClassValues( - species.getCloseUtilizationVolumeNetOfDecayByUtilization(), UtilizationClass.UTIL_CLASSES - ) - ); - species.getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization().set( - ucAll, - sumUtilizationClassValues( - species.getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization(), - UtilizationClass.UTIL_CLASSES - ) - ); - - if (species.getBaseAreaByUtilization().get(ucAll) > 0.0f) { - species.getQuadraticMeanDiameterByUtilization().set( - ucAll, - BaseAreaTreeDensityDiameter.quadMeanDiameter( - species.getBaseAreaByUtilization().get(ucAll), - species.getTreesPerHectareByUtilization().get(ucAll) - ) - ); - } - } - - // Set the layer's uc All values (for summable types) to the sum of those of the - // individual species. - - layer.getBaseAreaByUtilization() - .set(ucAll, sumSpeciesUtilizationClassValues(layer, "BaseArea", UtilizationClass.ALL)); - layer.getTreesPerHectareByUtilization() - .set(ucAll, sumSpeciesUtilizationClassValues(layer, "TreesPerHectare", UtilizationClass.ALL)); - layer.getWholeStemVolumeByUtilization() - .set(ucAll, sumSpeciesUtilizationClassValues(layer, "WholeStemVolume", UtilizationClass.ALL)); - layer.getCloseUtilizationVolumeByUtilization() - .set(ucAll, sumSpeciesUtilizationClassValues(layer, "CloseUtilizationVolume", UtilizationClass.ALL)); - layer.getCloseUtilizationVolumeNetOfDecayByUtilization().set( - ucAll, sumSpeciesUtilizationClassValues(layer, "CloseUtilizationVolumeNetOfDecay", UtilizationClass.ALL) - ); - layer.getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization().set( - ucAll, - sumSpeciesUtilizationClassValues( - layer, "CloseUtilizationVolumeNetOfDecayAndWaste", UtilizationClass.ALL - ) - ); - - // Calculate the layer's uc All values for quad-mean-diameter and lorey height - - float sumLoreyHeightByBasalAreaSmall = 0.0f; - float sumBasalAreaSmall = 0.0f; - float sumLoreyHeightByBasalAreaAll = 0.0f; - - for (var species : layer.getSpecies().values()) { - sumLoreyHeightByBasalAreaSmall += species.getLoreyHeightByUtilization().get(ucSmall) - * species.getBaseAreaByUtilization().get(ucSmall); - sumBasalAreaSmall += species.getBaseAreaByUtilization().get(ucSmall); - sumLoreyHeightByBasalAreaAll += species.getLoreyHeightByUtilization().get(ucAll) - * species.getBaseAreaByUtilization().get(ucAll); - } - - if (layer.getBaseAreaByUtilization().get(ucAll) > 0.0f) { - layer.getQuadraticMeanDiameterByUtilization().set( - ucAll, - BaseAreaTreeDensityDiameter.quadMeanDiameter( - layer.getBaseAreaByUtilization().get(ucAll), - layer.getTreesPerHectareByUtilization().get(ucAll) - ) - ); - layer.getLoreyHeightByUtilization() - .set(ucAll, sumLoreyHeightByBasalAreaAll / layer.getBaseAreaByUtilization().get(ucAll)); - } - - // Calculate the layer's lorey height uc Small value - - if (sumBasalAreaSmall > 0.0f) { - layer.getLoreyHeightByUtilization().set(ucSmall, sumLoreyHeightByBasalAreaSmall / sumBasalAreaSmall); - } - - // Finally, set the layer's summable UC values (other than All, which was computed above) to - // the sums of those of each of the species. - - for (UtilizationClass uc : UtilizationClass.ALL_CLASSES) { - layer.getBaseAreaByUtilization().set(uc, sumSpeciesUtilizationClassValues(layer, "BaseArea", uc)); - layer.getTreesPerHectareByUtilization() - .set(uc, sumSpeciesUtilizationClassValues(layer, "TreesPerHectare", uc)); - layer.getWholeStemVolumeByUtilization() - .set(uc, sumSpeciesUtilizationClassValues(layer, "WholeStemVolume", uc)); - layer.getCloseUtilizationVolumeByUtilization() - .set(uc, sumSpeciesUtilizationClassValues(layer, "CloseUtilizationVolume", uc)); - layer.getCloseUtilizationVolumeNetOfDecayByUtilization() - .set(uc, sumSpeciesUtilizationClassValues(layer, "CloseUtilizationVolumeNetOfDecay", uc)); - layer.getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization() - .set(uc, sumSpeciesUtilizationClassValues(layer, "CloseUtilizationVolumeNetOfDecayAndWaste", uc)); - } - - return layer; - } - - private static float sumUtilizationClassValues(UtilizationVector ucValues, List subjects) { - float sum = 0.0f; - - for (UtilizationClass uc : UtilizationClass.values()) { - if (subjects.contains(uc)) { - sum += ucValues.get(uc); - } - } - - return sum; - } - - private static float sumSpeciesUtilizationClassValues(VdypLayer layer, String uvName, UtilizationClass uc) { - float sum = 0.0f; - - for (VdypSpecies species : layer.getSpecies().values()) { - switch (uvName) { - case "CloseUtilizationVolumeNetOfDecayWasteAndBreakage": - sum += species.getCloseUtilizationVolumeNetOfDecayWasteAndBreakageByUtilization().get(uc); - break; - case "CloseUtilizationVolumeNetOfDecayAndWaste": - sum += species.getCloseUtilizationVolumeNetOfDecayAndWasteByUtilization().get(uc); - break; - case "CloseUtilizationVolumeNetOfDecay": - sum += species.getCloseUtilizationVolumeNetOfDecayByUtilization().get(uc); - break; - case "CloseUtilizationVolume": - sum += species.getCloseUtilizationVolumeByUtilization().get(uc); - break; - case "WholeStemVolume": - sum += species.getWholeStemVolumeByUtilization().get(uc); - break; - case "TreesPerHectare": - sum += species.getTreesPerHectareByUtilization().get(uc); - break; - case "BaseArea": - sum += species.getBaseAreaByUtilization().get(uc); - break; - default: - throw new IllegalStateException(uvName + " is not a known utilization vector name"); - } - } - - return sum; - } } From 51774cd6ee601f7d262796cdbec4f83cf15c9d2a Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 18 Oct 2024 14:33:17 -0700 Subject: [PATCH 08/22] Improve Bank unit tests --- .../bc/gov/nrs/vdyp/processing_state/BankTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java index fa0b44267..c525be524 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java @@ -62,10 +62,23 @@ void before() throws IOException, ResourceParseException { lb.addSpecies(sb -> { sb.genus("H", controlMap); sb.baseArea(50f); + sb.addSite(ib->{ + ib.ageTotal(100); + ib.yearsToBreastHeight(5); + ib.siteIndex(0.6f); + ib.height(20f); + ib.siteCurveNumber(10); + }); }); lb.addSpecies(sb -> { sb.genus("S", controlMap); sb.baseArea(99.9f); + sb.addSite(ib->{ + ib.ageTotal(100); + ib.yearsToBreastHeight(5); + ib.siteIndex(0.6f); + ib.height(20f); + }); }); }); }); From 3ed2b79d242a0e686908c4871f051d366679fdb0 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 18 Oct 2024 15:22:27 -0700 Subject: [PATCH 09/22] Fix formatting --- .../java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java index c525be524..e70dbc0f1 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java @@ -62,7 +62,7 @@ void before() throws IOException, ResourceParseException { lb.addSpecies(sb -> { sb.genus("H", controlMap); sb.baseArea(50f); - sb.addSite(ib->{ + sb.addSite(ib -> { ib.ageTotal(100); ib.yearsToBreastHeight(5); ib.siteIndex(0.6f); @@ -73,11 +73,11 @@ void before() throws IOException, ResourceParseException { lb.addSpecies(sb -> { sb.genus("S", controlMap); sb.baseArea(99.9f); - sb.addSite(ib->{ + sb.addSite(ib -> { ib.ageTotal(100); ib.yearsToBreastHeight(5); ib.siteIndex(0.6f); - ib.height(20f); + ib.height(20f); }); }); }); From c5efff376a84974b1521ed0e8d56a9f9392d4ed8 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 18 Oct 2024 15:43:34 -0700 Subject: [PATCH 10/22] Move VDYP Model object parsers from forward to common --- .../model}/VdypPolygonDescriptionParser.java | 2 +- .../io/parse/model}/VdypPolygonParser.java | 2 +- .../io/parse/model}/VdypSpeciesParser.java | 14 ++-- .../parse/model}/VdypUtilizationParser.java | 20 +++--- .../nrs/vdyp/io/parse/value/ValueParser.java | 56 ++++++++++++++++ .../nrs/vdyp/processing_state/BankTest.java | 24 +++++++ .../vdyp/forward/ForwardControlParser.java | 8 +-- .../parsers/VdypForwardDefaultingParser.java | 64 ------------------- .../AbstractForwardProcessingEngineTest.java | 6 +- .../forward/ForwardPolygonParserTest.java | 2 +- ...wardProcessorCheckpointGenerationTest.java | 6 +- .../forward/ForwardProcessorEndToEndTest.java | 6 +- .../forward/ForwardSpeciesParserTest.java | 2 +- .../forward/ForwardUtilizationParserTest.java | 2 +- .../test/Vdyp7OutputControlParser.java | 8 +-- 15 files changed, 119 insertions(+), 103 deletions(-) rename lib/{vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers => vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model}/VdypPolygonDescriptionParser.java (98%) rename lib/{vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers => vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model}/VdypPolygonParser.java (99%) rename lib/{vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers => vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model}/VdypSpeciesParser.java (94%) rename lib/{vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers => vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model}/VdypUtilizationParser.java (88%) delete mode 100644 lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypForwardDefaultingParser.java diff --git a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypPolygonDescriptionParser.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonDescriptionParser.java similarity index 98% rename from lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypPolygonDescriptionParser.java rename to lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonDescriptionParser.java index 4fa9c991f..be1d07dfb 100644 --- a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypPolygonDescriptionParser.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonDescriptionParser.java @@ -1,4 +1,4 @@ -package ca.bc.gov.nrs.vdyp.forward.parsers; +package ca.bc.gov.nrs.vdyp.io.parse.model; import java.io.IOException; import java.util.Map; diff --git a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypPolygonParser.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonParser.java similarity index 99% rename from lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypPolygonParser.java rename to lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonParser.java index b949f320a..9869af14e 100644 --- a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypPolygonParser.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonParser.java @@ -1,4 +1,4 @@ -package ca.bc.gov.nrs.vdyp.forward.parsers; +package ca.bc.gov.nrs.vdyp.io.parse.model; import java.io.IOException; import java.text.MessageFormat; diff --git a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypSpeciesParser.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java similarity index 94% rename from lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypSpeciesParser.java rename to lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java index 968de8931..b61b100fa 100644 --- a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypSpeciesParser.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java @@ -1,4 +1,4 @@ -package ca.bc.gov.nrs.vdyp.forward.parsers; +package ca.bc.gov.nrs.vdyp.io.parse.model; import java.io.IOException; import java.util.ArrayList; @@ -86,13 +86,13 @@ public ControlKey getControlKey() { .value(5, PERCENT_SPECIES_2, ControlledValueParser.optional(ValueParser.PERCENTAGE)) .value(3, SPECIES_3, ControlledValueParser.optional(ControlledValueParser.SPECIES)) .value(5, PERCENT_SPECIES_3, ControlledValueParser.optional(ValueParser.PERCENTAGE)) - .value(6, SITE_INDEX, VdypForwardDefaultingParser.FLOAT_WITH_DEFAULT) - .value(6, DOMINANT_HEIGHT, VdypForwardDefaultingParser.FLOAT_WITH_DEFAULT) - .value(6, TOTAL_AGE, VdypForwardDefaultingParser.FLOAT_WITH_DEFAULT) - .value(6, AGE_AT_BREAST_HEIGHT, VdypForwardDefaultingParser.FLOAT_WITH_DEFAULT) - .value(6, YEARS_TO_BREAST_HEIGHT, VdypForwardDefaultingParser.FLOAT_WITH_DEFAULT) + .value(6, SITE_INDEX, ValueParser.FLOAT_WITH_DEFAULT) + .value(6, DOMINANT_HEIGHT, ValueParser.FLOAT_WITH_DEFAULT) + .value(6, TOTAL_AGE, ValueParser.FLOAT_WITH_DEFAULT) + .value(6, AGE_AT_BREAST_HEIGHT, ValueParser.FLOAT_WITH_DEFAULT) + .value(6, YEARS_TO_BREAST_HEIGHT, ValueParser.FLOAT_WITH_DEFAULT) .value(2, IS_PRIMARY_SPECIES, ControlledValueParser.optional(ValueParser.LOGICAL_0_1)) - .value(3, SITE_CURVE_NUMBER, VdypForwardDefaultingParser.INTEGER_WITH_DEFAULT); + .value(3, SITE_CURVE_NUMBER, ValueParser.INTEGER_WITH_DEFAULT); var is = fileResolver.resolveForInput(fileName); diff --git a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypUtilizationParser.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypUtilizationParser.java similarity index 88% rename from lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypUtilizationParser.java rename to lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypUtilizationParser.java index a8b10f5a6..d9857215c 100644 --- a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypUtilizationParser.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypUtilizationParser.java @@ -1,4 +1,4 @@ -package ca.bc.gov.nrs.vdyp.forward.parsers; +package ca.bc.gov.nrs.vdyp.io.parse.model; import java.io.IOException; import java.util.Collection; @@ -66,15 +66,15 @@ public ControlKey getControlKey() { .space(1) // .value(2, GENUS, ControlledValueParser.optional(ValueParser.GENUS)) .value(3, UTILIZATION_CLASS_INDEX, ControlledValueParser.UTILIZATION_CLASS) - .value(9, BASAL_AREA, VdypForwardDefaultingParser.FLOAT_WITH_DEFAULT) - .value(9, LIVE_TREES_PER_HECTARE, VdypForwardDefaultingParser.FLOAT_WITH_DEFAULT) - .value(9, LOREY_HEIGHT, VdypForwardDefaultingParser.FLOAT_WITH_DEFAULT) - .value(9, WHOLE_STEM_VOLUME, VdypForwardDefaultingParser.FLOAT_WITH_DEFAULT) - .value(9, CLOSE_UTIL_VOLUME, VdypForwardDefaultingParser.FLOAT_WITH_DEFAULT) - .value(9, CU_VOLUME_LESS_DECAY, VdypForwardDefaultingParser.FLOAT_WITH_DEFAULT) - .value(9, CU_VOLUME_LESS_DECAY_WASTAGE, VdypForwardDefaultingParser.FLOAT_WITH_DEFAULT) - .value(9, CU_VOLUME_LESS_DECAY_WASTAGE_BREAKAGE, VdypForwardDefaultingParser.FLOAT_WITH_DEFAULT) - .value(6, QUADRATIC_MEAN_DIAMETER_BREAST_HEIGHT, VdypForwardDefaultingParser.FLOAT_WITH_DEFAULT); + .value(9, BASAL_AREA, ValueParser.FLOAT_WITH_DEFAULT) + .value(9, LIVE_TREES_PER_HECTARE, ValueParser.FLOAT_WITH_DEFAULT) + .value(9, LOREY_HEIGHT, ValueParser.FLOAT_WITH_DEFAULT) + .value(9, WHOLE_STEM_VOLUME, ValueParser.FLOAT_WITH_DEFAULT) + .value(9, CLOSE_UTIL_VOLUME, ValueParser.FLOAT_WITH_DEFAULT) + .value(9, CU_VOLUME_LESS_DECAY, ValueParser.FLOAT_WITH_DEFAULT) + .value(9, CU_VOLUME_LESS_DECAY_WASTAGE, ValueParser.FLOAT_WITH_DEFAULT) + .value(9, CU_VOLUME_LESS_DECAY_WASTAGE_BREAKAGE, ValueParser.FLOAT_WITH_DEFAULT) + .value(6, QUADRATIC_MEAN_DIAMETER_BREAST_HEIGHT, ValueParser.FLOAT_WITH_DEFAULT); var is = fileResolver.resolveForInput(fileName); diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/value/ValueParser.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/value/ValueParser.java index 1277dd5d5..87b59fd6f 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/value/ValueParser.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/value/ValueParser.java @@ -11,9 +11,12 @@ import java.util.function.Function; import java.util.function.Predicate; +import org.apache.commons.lang3.StringUtils; + import ca.bc.gov.nrs.vdyp.common.ValueOrMarker; import ca.bc.gov.nrs.vdyp.model.LayerType; import ca.bc.gov.nrs.vdyp.model.Region; +import ca.bc.gov.nrs.vdyp.model.VdypEntity; /** * Parses a string to a value @@ -42,6 +45,42 @@ default T parse(String string, Map control) throws ValueParseExc return this.parse(string); } + /** + * Validate that a parsed value is greater than 0 and less than (or, if includeMax is true, equal to) max. + * Additionally, if the value is -9.0, it is considered "not present" and Float.NaN is returns. + * + * @param parser underlying parser + * @param max the upper bound + * @param includeMax is the upper bound inclusive + * @param name Name for the value to use in the parse error if it is out of the range. + */ + static > ValueParser rangeSilentWithDefaulting( + ValueParser parser, T min, boolean includeMin, T max, boolean includeMax, T missingIndicator, + T defaultValue, String name + ) { + return ValueParser.validate(s -> { + var result = defaultValue; + if (!StringUtils.isEmpty(s)) { + result = parser.parse(s); + if (missingIndicator.equals(result)) { + result = defaultValue; + } + } + return result; + }, result -> { + if (!defaultValue.equals(result) && (result.compareTo(max) > (includeMax ? 0 : -1) + || result.compareTo(min) < (includeMin ? 0 : 1))) { + return Optional.of( + String.format( + "{} must be between {} ({}) and {} ({})", name, min, + includeMin ? "inclusive" : "exclusive", max, includeMax ? "inclusive" : "exclusive" + ) + ); + } + return Optional.empty(); + }); + } + public static interface JavaNumberParser { U parse(String s) throws NumberFormatException; } @@ -476,4 +515,21 @@ public static ValueParser callback(ValueParser delegate, Consumer c }; } + /** + * Parser for non-negative single precision floats with default -9.0. -9.0 results in an + * VdypEntity.MISSING_FLOAT_VALUE being returned. All other negative values, and those greater than Float.MAX_VALUE, + * result in an error. + */ + ValueParser FLOAT_WITH_DEFAULT = rangeSilentWithDefaulting( + FLOAT, 0.0f, true, Float.MAX_VALUE, true, -9.0f, VdypEntity.MISSING_FLOAT_VALUE, "non-negative float" + ); + + /** + * Parser for non-negative integers with default -9. -9 results in VdypEntity.MISSING_INTEGER_VALUE being returned. + * All other negative values, and those > Float.MAX_VALUE, result in an error. + */ + ValueParser INTEGER_WITH_DEFAULT = rangeSilentWithDefaulting( + INTEGER, 0, true, Integer.MAX_VALUE, true, -9, VdypEntity.MISSING_INTEGER_VALUE, "non-negative integer" + ); + } diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java index e70dbc0f1..89aac59c1 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java @@ -8,11 +8,15 @@ import java.util.List; import java.util.Map; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import ca.bc.gov.nrs.vdyp.application.ProcessingException; import ca.bc.gov.nrs.vdyp.common.Utils; +import ca.bc.gov.nrs.vdyp.common_calculators.BaseAreaTreeDensityDiameter; import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; import ca.bc.gov.nrs.vdyp.model.LayerType; import ca.bc.gov.nrs.vdyp.model.UtilizationClass; @@ -79,8 +83,27 @@ void before() throws IOException, ResourceParseException { ib.siteIndex(0.6f); ib.height(20f); }); + + sb.quadMeanDiameter(25); + sb.baseArea(26); + sb.treesPerHectare(BaseAreaTreeDensityDiameter.treesPerHectare(26, 25)); + sb.loreyHeight(227); + sb.closeUtilizationVolumeByUtilization(42); + sb.closeUtilizationVolumeNetOfDecayByUtilization(41); + sb.closeUtilizationVolumeNetOfDecayAndWasteByUtilization(40); + sb.closeUtilizationVolumeNetOfDecayWasteAndBreakageByUtilization(39); }); + + lb.quadraticMeanDiameterByUtilization(21); + lb.baseAreaByUtilization(22); + lb.treesPerHectareByUtilization(BaseAreaTreeDensityDiameter.treesPerHectare(22, 21)); + lb.loreyHeightByUtilization(24); + lb.closeUtilizationVolumeByUtilization(42); + lb.closeUtilizationVolumeNetOfDecayByUtilization(41); + lb.closeUtilizationVolumeNetOfDecayAndWasteByUtilization(40); + lb.closeUtilizationVolumeNetOfDecayWasteAndBreakageByUtilization(39); }); + }); } @@ -290,4 +313,5 @@ private void verifyBankSpeciesMatchesSpecies(Bank bank, int index, VdypSpecies s assertThat(bank.siteCurveNumbers[index], is(VdypEntity.MISSING_INTEGER_VALUE)); }); } + } diff --git a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardControlParser.java b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardControlParser.java index e38df0d84..f11708055 100644 --- a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardControlParser.java +++ b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/ForwardControlParser.java @@ -17,10 +17,6 @@ import ca.bc.gov.nrs.vdyp.common.ControlKey; import ca.bc.gov.nrs.vdyp.common.Utils; import ca.bc.gov.nrs.vdyp.forward.parsers.ForwardControlVariableParser; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypPolygonDescriptionParser; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypPolygonParser; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypSpeciesParser; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypUtilizationParser; import ca.bc.gov.nrs.vdyp.io.FileResolver; import ca.bc.gov.nrs.vdyp.io.parse.coe.BasalAreaGrowthEmpiricalParser; import ca.bc.gov.nrs.vdyp.io.parse.coe.BasalAreaGrowthFiatParser; @@ -71,6 +67,10 @@ import ca.bc.gov.nrs.vdyp.io.parse.control.BaseControlParser; import ca.bc.gov.nrs.vdyp.io.parse.control.ControlMapValueReplacer; import ca.bc.gov.nrs.vdyp.io.parse.control.ResourceControlMapModifier; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypPolygonDescriptionParser; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypPolygonParser; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypSpeciesParser; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypUtilizationParser; import ca.bc.gov.nrs.vdyp.io.parse.value.ValueParser; /** diff --git a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypForwardDefaultingParser.java b/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypForwardDefaultingParser.java deleted file mode 100644 index 3742d5417..000000000 --- a/lib/vdyp-forward/src/main/java/ca/bc/gov/nrs/vdyp/forward/parsers/VdypForwardDefaultingParser.java +++ /dev/null @@ -1,64 +0,0 @@ -package ca.bc.gov.nrs.vdyp.forward.parsers; - -import java.util.Optional; - -import org.apache.commons.lang3.StringUtils; - -import ca.bc.gov.nrs.vdyp.io.parse.value.ValueParser; -import ca.bc.gov.nrs.vdyp.model.VdypEntity; - -public interface VdypForwardDefaultingParser extends ValueParser { - - /** - * Parser for non-negative single precision floats with default -9.0. -9.0 results in an - * VdypEntity.MISSING_FLOAT_VALUE being returned. All other negative values, and those greater than Float.MAX_VALUE, - * result in an error. - */ - public static final ValueParser FLOAT_WITH_DEFAULT = rangeSilentWithDefaulting( - FLOAT, 0.0f, true, Float.MAX_VALUE, true, -9.0f, VdypEntity.MISSING_FLOAT_VALUE, "non-negative float" - ); - - /** - * Parser for non-negative integers with default -9. -9 results in VdypEntity.MISSING_INTEGER_VALUE being returned. - * All other negative values, and those > Float.MAX_VALUE, result in an error. - */ - public static final ValueParser INTEGER_WITH_DEFAULT = rangeSilentWithDefaulting( - INTEGER, 0, true, Integer.MAX_VALUE, true, -9, VdypEntity.MISSING_INTEGER_VALUE, "non-negative integer" - ); - - /** - * Validate that a parsed value is greater than 0 and less than (or, if includeMax is true, equal to) max. - * Additionally, if the value is -9.0, it is considered "not present" and Float.NaN is returns. - * - * @param parser underlying parser - * @param max the upper bound - * @param includeMax is the upper bound inclusive - * @param name Name for the value to use in the parse error if it is out of the range. - */ - public static > ValueParser rangeSilentWithDefaulting( - ValueParser parser, T min, boolean includeMin, T max, boolean includeMax, T missingIndicator, - T defaultValue, String name - ) { - return ValueParser.validate(s -> { - var result = defaultValue; - if (!StringUtils.isEmpty(s)) { - result = parser.parse(s); - if (missingIndicator.equals(result)) { - result = defaultValue; - } - } - return result; - }, result -> { - if (!defaultValue.equals(result) && (result.compareTo(max) > (includeMax ? 0 : -1) - || result.compareTo(min) < (includeMin ? 0 : 1))) { - return Optional.of( - String.format( - "{} must be between {} ({}) and {} ({})", name, min, - includeMin ? "inclusive" : "exclusive", max, includeMax ? "inclusive" : "exclusive" - ) - ); - } - return Optional.empty(); - }); - } -} diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/AbstractForwardProcessingEngineTest.java b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/AbstractForwardProcessingEngineTest.java index a3884dfff..5a776e8bc 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/AbstractForwardProcessingEngineTest.java +++ b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/AbstractForwardProcessingEngineTest.java @@ -9,11 +9,11 @@ import ca.bc.gov.nrs.vdyp.application.ProcessingException; import ca.bc.gov.nrs.vdyp.common.ControlKey; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypPolygonParser; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypSpeciesParser; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypUtilizationParser; import ca.bc.gov.nrs.vdyp.forward.test.ForwardTestUtils; import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypPolygonParser; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypSpeciesParser; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypUtilizationParser; import ca.bc.gov.nrs.vdyp.io.parse.streaming.StreamingParser; import ca.bc.gov.nrs.vdyp.io.parse.streaming.StreamingParserFactory; import ca.bc.gov.nrs.vdyp.model.PolygonIdentifier; diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardPolygonParserTest.java b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardPolygonParserTest.java index 44008575d..1804e9f1a 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardPolygonParserTest.java +++ b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardPolygonParserTest.java @@ -15,7 +15,7 @@ import org.junit.jupiter.api.Test; import ca.bc.gov.nrs.vdyp.common.ControlKey; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypPolygonParser; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypPolygonParser; import ca.bc.gov.nrs.vdyp.io.parse.streaming.StreamingParser; import ca.bc.gov.nrs.vdyp.io.parse.streaming.StreamingParserFactory; import ca.bc.gov.nrs.vdyp.model.PolygonMode; diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorCheckpointGenerationTest.java b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorCheckpointGenerationTest.java index 6df64445a..d41d83fd0 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorCheckpointGenerationTest.java +++ b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorCheckpointGenerationTest.java @@ -18,14 +18,14 @@ import ca.bc.gov.nrs.vdyp.application.Pass; import ca.bc.gov.nrs.vdyp.application.ProcessingException; import ca.bc.gov.nrs.vdyp.common.ControlKey; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypPolygonParser; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypSpeciesParser; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypUtilizationParser; import ca.bc.gov.nrs.vdyp.io.FileResolver; import ca.bc.gov.nrs.vdyp.io.FileSystemFileResolver; import ca.bc.gov.nrs.vdyp.io.parse.coe.BecDefinitionParser; import ca.bc.gov.nrs.vdyp.io.parse.coe.GenusDefinitionParser; import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypPolygonParser; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypSpeciesParser; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypUtilizationParser; import ca.bc.gov.nrs.vdyp.model.VdypPolygon; import ca.bc.gov.nrs.vdyp.test.TestUtils; diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorEndToEndTest.java b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorEndToEndTest.java index 3e16c1843..84fff4fa4 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorEndToEndTest.java +++ b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardProcessorEndToEndTest.java @@ -22,9 +22,6 @@ import ca.bc.gov.nrs.vdyp.application.Pass; import ca.bc.gov.nrs.vdyp.application.ProcessingException; import ca.bc.gov.nrs.vdyp.common.ControlKey; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypPolygonParser; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypSpeciesParser; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypUtilizationParser; import ca.bc.gov.nrs.vdyp.io.FileResolver; import ca.bc.gov.nrs.vdyp.io.FileSystemFileResolver; import ca.bc.gov.nrs.vdyp.io.parse.coe.BecDefinitionParser; @@ -33,6 +30,9 @@ import ca.bc.gov.nrs.vdyp.io.parse.coe.GenusDefinitionParser; import ca.bc.gov.nrs.vdyp.io.parse.coe.VolumeEquationGroupParser; import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypPolygonParser; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypSpeciesParser; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypUtilizationParser; import ca.bc.gov.nrs.vdyp.model.LayerType; import ca.bc.gov.nrs.vdyp.model.UtilizationVector; import ca.bc.gov.nrs.vdyp.model.VdypLayer; diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardSpeciesParserTest.java b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardSpeciesParserTest.java index 143809bcf..2dfc2a487 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardSpeciesParserTest.java +++ b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardSpeciesParserTest.java @@ -21,7 +21,7 @@ import org.junit.jupiter.api.Test; import ca.bc.gov.nrs.vdyp.common.ControlKey; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypSpeciesParser; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypSpeciesParser; import ca.bc.gov.nrs.vdyp.io.parse.streaming.StreamingParser; import ca.bc.gov.nrs.vdyp.io.parse.streaming.StreamingParserFactory; import ca.bc.gov.nrs.vdyp.model.LayerType; diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardUtilizationParserTest.java b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardUtilizationParserTest.java index f9ddebc43..363b42656 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardUtilizationParserTest.java +++ b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/ForwardUtilizationParserTest.java @@ -19,7 +19,7 @@ import org.junit.jupiter.api.Test; import ca.bc.gov.nrs.vdyp.common.ControlKey; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypUtilizationParser; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypUtilizationParser; import ca.bc.gov.nrs.vdyp.io.parse.streaming.StreamingParser; import ca.bc.gov.nrs.vdyp.io.parse.streaming.StreamingParserFactory; import ca.bc.gov.nrs.vdyp.model.LayerType; diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/test/Vdyp7OutputControlParser.java b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/test/Vdyp7OutputControlParser.java index b3d57f6c4..0e06f2a58 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/test/Vdyp7OutputControlParser.java +++ b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/test/Vdyp7OutputControlParser.java @@ -15,10 +15,6 @@ import ca.bc.gov.nrs.vdyp.common.ControlKey; import ca.bc.gov.nrs.vdyp.common.Utils; import ca.bc.gov.nrs.vdyp.forward.parsers.ForwardControlVariableParser; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypPolygonDescriptionParser; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypPolygonParser; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypSpeciesParser; -import ca.bc.gov.nrs.vdyp.forward.parsers.VdypUtilizationParser; import ca.bc.gov.nrs.vdyp.io.FileResolver; import ca.bc.gov.nrs.vdyp.io.parse.coe.BecDefinitionParser; import ca.bc.gov.nrs.vdyp.io.parse.coe.BreakageEquationGroupParser; @@ -29,6 +25,10 @@ import ca.bc.gov.nrs.vdyp.io.parse.control.BaseControlParser; import ca.bc.gov.nrs.vdyp.io.parse.control.ControlMapValueReplacer; import ca.bc.gov.nrs.vdyp.io.parse.control.ResourceControlMapModifier; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypPolygonDescriptionParser; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypPolygonParser; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypSpeciesParser; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypUtilizationParser; import ca.bc.gov.nrs.vdyp.io.parse.value.ValueParser; /** From a718fb3281e2aebb018d7d969484545001d1231f Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Mon, 21 Oct 2024 12:08:10 -0700 Subject: [PATCH 11/22] Unit test for back processing engine --- .../vdyp/back/BackProcessingEngineTest.java | 386 ++++++++++++++++++ 1 file changed, 386 insertions(+) create mode 100644 lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java diff --git a/lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java b/lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java new file mode 100644 index 000000000..05be1a747 --- /dev/null +++ b/lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java @@ -0,0 +1,386 @@ +package ca.bc.gov.nrs.vdyp.back; + +import static ca.bc.gov.nrs.vdyp.test.VdypMatchers.notPresent; +import static ca.bc.gov.nrs.vdyp.test.VdypMatchers.present; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import ca.bc.gov.nrs.vdyp.application.ProcessingException; +import ca.bc.gov.nrs.vdyp.back.processing_state.BackProcessingState; +import ca.bc.gov.nrs.vdyp.common.ControlKey; +import ca.bc.gov.nrs.vdyp.common.Utils; +import ca.bc.gov.nrs.vdyp.common_calculators.BaseAreaTreeDensityDiameter; +import ca.bc.gov.nrs.vdyp.math.FloatMath; +import ca.bc.gov.nrs.vdyp.model.BecLookup; +import ca.bc.gov.nrs.vdyp.model.ComponentSizeLimits; +import ca.bc.gov.nrs.vdyp.model.LayerType; +import ca.bc.gov.nrs.vdyp.model.MatrixMap2; +import ca.bc.gov.nrs.vdyp.model.MatrixMap2Impl; +import ca.bc.gov.nrs.vdyp.model.MatrixMap3; +import ca.bc.gov.nrs.vdyp.model.MatrixMap3Impl; +import ca.bc.gov.nrs.vdyp.model.UtilizationClass; +import ca.bc.gov.nrs.vdyp.model.UtilizationClassVariable; +import ca.bc.gov.nrs.vdyp.model.VdypPolygon; +import ca.bc.gov.nrs.vdyp.model.VolumeVariable; +import ca.bc.gov.nrs.vdyp.test.TestUtils; + +class BackProcessingEngineTest { + + BackProcessingEngine engine; + + Map controlMap; + + BecLookup becLookup; + + @BeforeEach + void setup() { + engine = new BackProcessingEngine(); + + controlMap = TestUtils.loadControlMap(); // TODO switch to appropriate control file for Back. + + becLookup = Utils.parsedControl(controlMap, ControlKey.BEC_DEF, BecLookup.class).get(); + + } + + @Nested + class Prepare { + + public BackProcessingState primaryOnlyWithSingleSpecies() throws ProcessingException { + + var polygon = VdypPolygon.build(pb -> { + pb.polygonIdentifier("Test", 2024); + + pb.percentAvailable(80f); + pb.biogeoclimaticZone(becLookup.get("IDF").get()); + pb.forestInventoryZone(""); + + pb.addLayer(lb -> { + lb.layerType(LayerType.PRIMARY); + + final float ba = 5.0969491f; + final float hl = 22.9584007f; + final float dq = 31.5006275f; + final float tph = BaseAreaTreeDensityDiameter.treesPerHectare(ba, dq); + lb.addSpecies(sb -> { + sb.genus("F", controlMap); + sb.baseArea(ba); + sb.loreyHeight(hl); + sb.quadMeanDiameter(dq); + sb.treesPerHectare(tph); + }); + + lb.baseAreaByUtilization(ba); + lb.loreyHeightByUtilization(hl); + lb.quadraticMeanDiameterByUtilization(dq); + lb.treesPerHectareByUtilization(tph); + }); + }); + + var state = new BackProcessingState(controlMap); + + state.setPolygon(polygon); + + @SuppressWarnings("unchecked") + MatrixMap3[] cvVolume = new MatrixMap3[] { null, + new MatrixMap3Impl( + List.of(UtilizationClass.values()), List.of(VolumeVariable.values()), + List.of(LayerType.values()), + (uc, vv, lt) -> 11f + vv.ordinal() * 2f + uc.ordinal() * 3f + lt.ordinal() * 5f + ) }; + + @SuppressWarnings("unchecked") + MatrixMap2[] cvBa = new MatrixMap2[] { null, + new MatrixMap2Impl( + List.of(UtilizationClass.values()), List.of(LayerType.values()), + (uc, lt) -> 13f + uc.ordinal() * 3f + lt.ordinal() * 5f + ) }; + + @SuppressWarnings("unchecked") + MatrixMap2[] cvDq = new MatrixMap2[] { null, + new MatrixMap2Impl( + List.of(UtilizationClass.values()), List.of(LayerType.values()), + (uc, lt) -> 17f + uc.ordinal() * 3f + lt.ordinal() * 5f + ) }; + @SuppressWarnings("unchecked") + Map[] cvSm = new EnumMap[] { null, + new EnumMap(UtilizationClassVariable.class) }; + + for (var uc : UtilizationClassVariable.values()) { + cvSm[1].put(uc, uc.ordinal() * 7f); + } + + state.getPrimaryLayerProcessingState().setCompatibilityVariableDetails(cvVolume, cvBa, cvDq, cvSm); + + return state; + }; + + public BackProcessingState primaryAndVeteran() throws ProcessingException { + var polygon = VdypPolygon.build(pb -> { + pb.polygonIdentifier("Test", 2024); + + pb.percentAvailable(80f); + pb.biogeoclimaticZone(becLookup.get("IDF").get()); + pb.forestInventoryZone(""); + + pb.addLayer(lb -> { + lb.layerType(LayerType.PRIMARY); + }); + pb.addLayer(lb -> { + lb.layerType(LayerType.VETERAN); + lb.baseAreaByUtilization(20f); + + lb.addSpecies(sb -> { + sb.genus("F", controlMap); + sb.baseArea(20f); + }); + }); + }); + + var state = new BackProcessingState(controlMap); + + state.setPolygon(polygon); + + return state; + }; + + @Nested + class SetBav { + + @Test + void testSetBavIfVeteranPresent() throws ProcessingException { + + var state = primaryAndVeteran(); + + engine.prepare(state); + + var result = state.getBaseAreaVeteran(); + + assertThat(result, present(is(20f))); + } + + @Test + void testSetBavIfVeteranPresentAndTotalIsWrong() throws ProcessingException { + var polygon = VdypPolygon.build(pb -> { + pb.polygonIdentifier("Test", 2024); + + pb.percentAvailable(80f); + pb.biogeoclimaticZone(becLookup.get("IDF").get()); + pb.forestInventoryZone(""); + + pb.addLayer(lb -> { + lb.layerType(LayerType.PRIMARY); + }); + pb.addLayer(lb -> { + lb.layerType(LayerType.VETERAN); + lb.baseAreaByUtilization(42f); // Not the sum of the base areas of the species + lb.addSpecies(sb -> { + sb.genus("F", controlMap); + sb.baseArea(20f); + }); + }); + }); + + var state = new BackProcessingState(controlMap); + + state.setPolygon(polygon); + engine.prepare(state); + + var result = state.getBaseAreaVeteran(); + + assertThat(result, present(is(20f))); // Should be calculated from the species + } + + @Test + void testSetBavIfNoVeteranPresent() throws ProcessingException { + + var state = primaryOnlyWithSingleSpecies(); + + engine.prepare(state); + + var result = state.getBaseAreaVeteran(); + + assertThat(result, notPresent()); + } + } + + @Nested + class SetCv { + + @Test + void testSingleSpecies() throws ProcessingException { + + var state = primaryOnlyWithSingleSpecies(); + + engine.prepare(state); + + final int specIndex = 1; // Only one species + + assertThat(state, backCV("getCVBasalArea", specIndex, UtilizationClass.ALL, is(16f))); + assertThat(state, backCV("getCVBasalArea", specIndex, UtilizationClass.U75TO125, is(19f))); + assertThat(state, backCV("getCVBasalArea", specIndex, UtilizationClass.U125TO175, is(22f))); + assertThat(state, backCV("getCVBasalArea", specIndex, UtilizationClass.U175TO225, is(25f))); + assertThat(state, backCV("getCVBasalArea", specIndex, UtilizationClass.OVER225, is(28f))); + + assertThat(state, backCV("getCVQuadraticMeanDiameter", specIndex, UtilizationClass.ALL, is(20f))); + assertThat(state, backCV("getCVQuadraticMeanDiameter", specIndex, UtilizationClass.U75TO125, is(23f))); + assertThat(state, backCV("getCVQuadraticMeanDiameter", specIndex, UtilizationClass.U125TO175, is(26f))); + assertThat(state, backCV("getCVQuadraticMeanDiameter", specIndex, UtilizationClass.U175TO225, is(29f))); + assertThat(state, backCV("getCVQuadraticMeanDiameter", specIndex, UtilizationClass.OVER225, is(32f))); + + } + + } + + @Nested + class SetLimits { + + @Test + void testSingleSpecies() throws ProcessingException { + + var state = primaryOnlyWithSingleSpecies(); + + engine.prepare(state); + + var result = state.getLimits(1); + + assertThat(result, componentSizeLimits(39.9f, 75.8f, 0.792f, 2.155f, 0.0001f)); + + } + } + + } + + static Matcher componentSizeLimits( + float loreyHeightMaximum, float quadMeanDiameterMaximum, float minQuadMeanDiameterLoreyHeightRatio, + float maxQuadMeanDiameterLoreyHeightRatio, float epsilon + ) { + return new TypeSafeDiagnosingMatcher(ComponentSizeLimits.class) { + + @Override + public void describeTo(Description description) { + description.appendText("ComponentSizeLimits with"); + description.appendText(" "); + description.appendText("loreyHeightMaximum = ").appendValue(loreyHeightMaximum); + description.appendText(", "); + description.appendText("quadMeanDiameterMaximum = ").appendValue(quadMeanDiameterMaximum); + description.appendText(", "); + description.appendText("minQuadMeanDiameterLoreyHeightRatio = ") + .appendValue(minQuadMeanDiameterLoreyHeightRatio); + description.appendText(", and "); + description.appendText("maxQuadMeanDiameterLoreyHeightRatio = ") + .appendValue(maxQuadMeanDiameterLoreyHeightRatio); + + description.appendText(" (all to within ±" + epsilon + ")"); + } + + static boolean matchFloatsSafely(float o1, float o2, float epsilon) { + if (Float.isNaN(o1) && Float.isNaN(o2)) + return true; + if (Float.isNaN(o1) || Float.isNaN(o2)) + return false; + return FloatMath.abs(o1 - o2) <= epsilon; + } + + @Override + protected boolean matchesSafely(ComponentSizeLimits item, Description mismatchDescription) { + boolean match = true; + if (!matchFloatsSafely(item.loreyHeightMaximum(), loreyHeightMaximum, epsilon)) { + match = false; + mismatchDescription.appendText("loreyHeightMaximum was ").appendValue(item.loreyHeightMaximum()); + } + if (!matchFloatsSafely(item.quadMeanDiameterMaximum(), quadMeanDiameterMaximum, epsilon)) { + if (!match) + mismatchDescription.appendText(", "); + match = false; + mismatchDescription.appendText("quadMeanDiameterMaximum was ") + .appendValue(item.quadMeanDiameterMaximum()); + } + if (!matchFloatsSafely( + item.minQuadMeanDiameterLoreyHeightRatio(), minQuadMeanDiameterLoreyHeightRatio, epsilon + )) { + if (!match) + mismatchDescription.appendText(", "); + match = false; + mismatchDescription.appendText("minQuadMeanDiameterLoreyHeightRatio was ") + .appendValue(item.minQuadMeanDiameterLoreyHeightRatio()); + } + if (!matchFloatsSafely( + item.maxQuadMeanDiameterLoreyHeightRatio(), maxQuadMeanDiameterLoreyHeightRatio, epsilon + )) { + if (!match) + mismatchDescription.appendText(", "); + match = false; + mismatchDescription.appendText("maxQuadMeanDiameterLoreyHeightRatio was ") + .appendValue(item.maxQuadMeanDiameterLoreyHeightRatio()); + } + return match; + } + + }; + } + + static Matcher backCV(String name, Object p1, Object p2, Matcher expected) { + return compatibilityVariable(name, expected, BackProcessingState.class, p1, p2); + } + + static Matcher backCV(String name, Object p1, Matcher expected) { + return compatibilityVariable(name, expected, BackProcessingState.class, p1); + } + + static Matcher + compatibilityVariable(String name, Matcher expected, Class klazz, Object... params) { + return new TypeSafeDiagnosingMatcher<>(klazz) { + + @Override + public void describeTo(Description description) { + description.appendText(name).appendValueList("(", ", ", ") ", params); + description.appendDescriptionOf(expected); + } + + @Override + protected boolean matchesSafely(T item, Description mismatchDescription) { + Method method; + try { + method = klazz.getMethod( + name, + (Class[]) Arrays.stream(params) + .map(o -> o instanceof Integer ? Integer.TYPE : o.getClass()).toArray(Class[]::new) + ); + } catch (NoSuchMethodException e) { + mismatchDescription.appendText("Method " + name + " does not exist"); + return false; + } + + float result; + try { + result = (float) method.invoke(item, params); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + mismatchDescription.appendText(e.getMessage()); + return false; + } + + if (expected.matches(result)) { + return true; + } + + expected.describeMismatch(result, mismatchDescription); + return false; + } + + }; + } +} From abedb9e5700704ab942ecc2ce02cf756e1e3d56b Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Mon, 21 Oct 2024 12:52:33 -0700 Subject: [PATCH 12/22] Working on unit tests for parser --- .../model/VdypUtilizationParserTest.java | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypUtilizationParserTest.java diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypUtilizationParserTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypUtilizationParserTest.java new file mode 100644 index 000000000..eac144f69 --- /dev/null +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypUtilizationParserTest.java @@ -0,0 +1,121 @@ +package ca.bc.gov.nrs.vdyp.io.parse.model; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.NoSuchElementException; + +import org.junit.jupiter.api.Test; + +import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; +import ca.bc.gov.nrs.vdyp.model.UtilizationClass; +import ca.bc.gov.nrs.vdyp.test.MockFileResolver; +import ca.bc.gov.nrs.vdyp.test.TestUtils; +import static ca.bc.gov.nrs.vdyp.test.VdypMatchers.*; + +class VdypUtilizationParserTest { + + @Test + void testEmpty() throws IOException, ResourceParseException { + var controlMap = TestUtils.loadControlMap(); + var resolver = new MockFileResolver("testResolver"); + + try (var is = TestUtils.makeInputStream("")) { + + resolver.addStream("test.dat", is); + + var parser = new VdypUtilizationParser().map("test.dat", resolver, controlMap); + + var stream = parser.get(); + + assertTrue(!stream.hasNext(), "stream is not empty"); + + assertThrows(NoSuchElementException.class, () -> stream.next()); + } + } + + @Test + void testOnePoly() throws IOException, ResourceParseException { + var controlMap = TestUtils.loadControlMap(); + var resolver = new MockFileResolver("testResolver"); + + try ( + var is = TestUtils.makeInputStream( + "01002 S000001 00 1970 P 0 -1 0.01513 5.24 7.0166 0.0630 0.0000 0.0000 0.0000 0.0000 6.1", + "01002 S000001 00 1970 P 0 0 44.93259 595.32 30.9724 620.9775 592.2023 580.1681 577.6229 549.0159 31.0", + "01002 S000001 00 1970 P 0 1 0.53100 64.82 -9.0000 2.5979 0.3834 0.3794 0.3788 0.3623 10.2", + "01002 S000001 00 1970 P 0 2 1.27855 71.93 -9.0000 9.1057 6.9245 6.8469 6.8324 6.5384 15.0", + "01002 S000001 00 1970 P 0 3 2.33020 73.60 -9.0000 22.4019 20.1244 19.8884 19.8375 18.9555 20.1", + "01002 S000001 00 1970 P 0 4 40.79285 384.98 -9.0000 586.8720 564.7699 553.0534 550.5741 523.1597 36.7", + "01002 S000001 00 1970 P 3 B -1 0.00000 0.00 8.0272 0.0000 0.0000 0.0000 0.0000 0.0000 6.1", + "01002 S000001 00 1970 P 3 B 0 0.40292 5.16 36.7553 6.2098 5.9592 5.8465 5.8163 5.5177 31.5", + "01002 S000001 00 1970 P 3 B 1 0.00502 0.76 -9.0000 0.0185 0.0009 0.0009 0.0009 0.0009 9.2", + "01002 S000001 00 1970 P 3 B 2 0.01363 0.93 -9.0000 0.0757 0.0498 0.0497 0.0496 0.0475 13.7", + "01002 S000001 00 1970 P 3 B 3 0.02284 0.88 -9.0000 0.1748 0.1521 0.1514 0.1512 0.1445 18.2", + "01002 S000001 00 1970 P 3 B 4 0.36143 2.60 -9.0000 5.9408 5.7564 5.6446 5.6146 5.3249 42.1", + "01002 S000001 00 1970 P 4 C -1 0.01243 4.40 6.4602 0.0507 0.0000 0.0000 0.0000 0.0000 6.0", + "01002 S000001 00 1970 P 4 C 0 5.04597 83.46 22.9584 43.4686 39.4400 36.2634 35.2930 32.9144 27.7", + "01002 S000001 00 1970 P 4 C 1 0.12822 16.12 -9.0000 0.6027 0.1116 0.1094 0.1090 0.1035 10.1", + "01002 S000001 00 1970 P 4 C 2 0.31003 17.87 -9.0000 1.9237 1.4103 1.3710 1.3628 1.2915 14.9", + "01002 S000001 00 1970 P 4 C 3 0.51339 16.55 -9.0000 3.8230 3.3162 3.2127 3.1859 3.0076 19.9", + "01002 S000001 00 1970 P 4 C 4 4.09434 32.92 -9.0000 37.1192 34.6019 31.5703 30.6352 28.5119 39.8", + "01002 S000001 00 1970 P 5 D -1 0.00155 0.47 10.6033 0.0078 0.0000 0.0000 0.0000 0.0000 6.5", + "01002 S000001 00 1970 P 5 D 0 29.30249 287.70 33.7440 459.5233 444.0844 436.5280 435.2818 413.6949 36.0", + "01002 S000001 00 1970 P 5 D 1 0.01412 1.64 -9.0000 0.1091 0.0571 0.0566 0.0565 0.0541 10.5", + "01002 S000001 00 1970 P 5 D 2 0.05128 2.69 -9.0000 0.5602 0.5048 0.5007 0.5005 0.4783 15.6", + "01002 S000001 00 1970 P 5 D 3 0.45736 13.82 -9.0000 6.0129 5.6414 5.5975 5.5948 5.3383 20.5", + "01002 S000001 00 1970 P 5 D 4 28.77972 269.56 -9.0000 452.8412 437.8810 430.3732 429.1300 407.8242 36.9", + "01002 S000001 00 1970 P 8 H -1 0.00000 0.00 7.5464 0.0000 0.0000 0.0000 0.0000 0.0000 -9.0", + "01002 S000001 00 1970 P 8 H 0 5.81006 167.90 22.7704 55.8878 49.8291 49.0742 48.8550 46.6828 21.0", + "01002 S000001 00 1970 P 8 H 1 0.36138 43.57 -9.0000 1.7385 0.1925 0.1913 0.1911 0.1834 10.3", + "01002 S000001 00 1970 P 8 H 2 0.82449 45.99 -9.0000 5.8666 4.4155 4.3846 4.3789 4.2023 15.1", + "01002 S000001 00 1970 P 8 H 3 1.07566 33.93 -9.0000 9.6521 8.5752 8.5019 8.4827 8.1397 20.1", + "01002 S000001 00 1970 P 8 H 4 3.54853 44.42 -9.0000 38.6306 36.6459 35.9963 35.8023 34.1574 31.9", + "01002 S000001 00 1970 P 15 S -1 0.00115 0.36 8.2003 0.0045 0.0000 0.0000 0.0000 0.0000 6.3", + "01002 S000001 00 1970 P 15 S 0 4.37115 51.10 32.0125 55.8879 52.8895 52.4561 52.3768 50.2060 33.0", + "01002 S000001 00 1970 P 15 S 1 0.02225 2.73 -9.0000 0.1291 0.0213 0.0212 0.0212 0.0204 10.2", + "01002 S000001 00 1970 P 15 S 2 0.07911 4.46 -9.0000 0.6795 0.5440 0.5410 0.5406 0.5189 15.0", + "01002 S000001 00 1970 P 15 S 3 0.26095 8.43 -9.0000 2.7391 2.4396 2.4250 2.4229 2.3254 19.9", + "01002 S000001 00 1970 P 15 S 4 4.00883 35.49 -9.0000 52.3402 49.8846 49.4689 49.3920 47.3414 37.9", + "01002 S000001 00 1970 " + ) + ) { + + resolver.addStream("test.dat", is); + + var parser = new VdypUtilizationParser().map("test.dat", resolver, controlMap); + + var stream = parser.get(); + + assertTrue(stream.hasNext(), "stream is empty"); + + var result = new ArrayList<>(stream.next()); // Array list makes it indexable so we can easily sample + // entries to test. Checking everything would be excessive. + + assertThat(result, hasSize(6 * UtilizationClass.values().length)); + + result.get(0); + + assertThat( + result.get(1), + allOf( + hasProperty("genus", notPresent()), hasProperty("genusIndex", is(0)), + hasProperty("ucIndex", is(UtilizationClass.ALL)), + hasProperty("basalArea", closeTo(44.93259f)), + hasProperty("liveTreesPerHectare", closeTo(595.32f)), + hasProperty("loreyHeight", closeTo(30.9724f)), + hasProperty("closeUtilizationVolume", closeTo(620.9775f)) + ) + ); + + assertTrue(!stream.hasNext(), "stream is empty"); + + assertThrows(NoSuchElementException.class, () -> stream.next()); + + } + } + +} From 2c36b843f1f561cdbb695b72f6818d4c7d368353 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Tue, 22 Oct 2024 12:27:06 -0700 Subject: [PATCH 13/22] Unit tests for Utilization parser --- .../model/VdypUtilizationParserTest.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypUtilizationParserTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypUtilizationParserTest.java index eac144f69..c9a3194cb 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypUtilizationParserTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypUtilizationParserTest.java @@ -97,8 +97,6 @@ void testOnePoly() throws IOException, ResourceParseException { assertThat(result, hasSize(6 * UtilizationClass.values().length)); - result.get(0); - assertThat( result.get(1), allOf( @@ -107,11 +105,29 @@ void testOnePoly() throws IOException, ResourceParseException { hasProperty("basalArea", closeTo(44.93259f)), hasProperty("liveTreesPerHectare", closeTo(595.32f)), hasProperty("loreyHeight", closeTo(30.9724f)), - hasProperty("closeUtilizationVolume", closeTo(620.9775f)) + hasProperty("wholeStemVolume", closeTo(620.9775f)), + hasProperty("closeUtilizationVolume", closeTo(592.2023f)), + hasProperty("cuVolumeMinusDecay", closeTo(580.1681f)), + hasProperty("cuVolumeMinusDecayWastage", closeTo(577.6229f)), + hasProperty("cuVolumeMinusDecayWastageBreakage", closeTo(549.0159f)) ) ); - assertTrue(!stream.hasNext(), "stream is empty"); + assertThat( + result.get(32), + allOf( + hasProperty("genus", present(is("S"))), hasProperty("genusIndex", is(15)), + hasProperty("ucIndex", is(UtilizationClass.U75TO125)), + hasProperty("basalArea", closeTo(0.02225f)), + hasProperty("liveTreesPerHectare", closeTo(2.73f)), + hasProperty("loreyHeight", is(Float.NaN)), hasProperty("wholeStemVolume", closeTo(0.1291f)), + hasProperty("closeUtilizationVolume", closeTo(0.0213f)), + hasProperty("cuVolumeMinusDecay", closeTo(0.0212f)), + hasProperty("cuVolumeMinusDecayWastage", closeTo(0.0212f)), + hasProperty("cuVolumeMinusDecayWastageBreakage", closeTo(0.0204f)) + ) + ); + assertTrue(!stream.hasNext(), "stream is not empty"); assertThrows(NoSuchElementException.class, () -> stream.next()); From f8fad2ab5695a4cde41202d494117d7eecddb761 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 23 Oct 2024 11:22:01 -0700 Subject: [PATCH 14/22] Unit test for parsing species --- .../io/parse/model/VdypSpeciesParserTest.java | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java new file mode 100644 index 000000000..92ef93b0b --- /dev/null +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java @@ -0,0 +1,182 @@ +package ca.bc.gov.nrs.vdyp.io.parse.model; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.NoSuchElementException; + +import org.junit.jupiter.api.Test; + +import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; +import ca.bc.gov.nrs.vdyp.model.LayerType; +import ca.bc.gov.nrs.vdyp.model.UtilizationClass; +import ca.bc.gov.nrs.vdyp.test.MockFileResolver; +import ca.bc.gov.nrs.vdyp.test.TestUtils; +import ca.bc.gov.nrs.vdyp.test.VdypMatchers; + +import static ca.bc.gov.nrs.vdyp.test.VdypMatchers.*; + +class VdypSpeciesParserTest { + + @Test + void testEmpty() throws IOException, ResourceParseException { + var controlMap = TestUtils.loadControlMap(); + var resolver = new MockFileResolver("testResolver"); + + try (var is = TestUtils.makeInputStream("")) { + + resolver.addStream("test.dat", is); + + var parser = new VdypSpeciesParser().map("test.dat", resolver, controlMap); + + var stream = parser.get(); + + assertTrue(!stream.hasNext(), "stream is not empty"); + + assertThrows(NoSuchElementException.class, () -> stream.next()); + } + } + + @Test + void testOnePoly() throws IOException, ResourceParseException { + var controlMap = TestUtils.loadControlMap(); + var resolver = new MockFileResolver("testResolver"); + + try ( + var is = TestUtils.makeInputStream( + "01002 S000001 00 1970 P 3 B B 100.0 0.0 0.0 0.0 -9.00 -9.00 -9.0 -9.0 -9.0 0 -9", + "01002 S000001 00 1970 P 4 C C 100.0 0.0 0.0 0.0 -9.00 -9.00 -9.0 -9.0 -9.0 0 -9", + "01002 S000001 00 1970 P 5 D D 100.0 0.0 0.0 0.0 35.00 35.30 55.0 54.0 1.0 1 13", + "01002 S000001 00 1970 P 8 H H 100.0 0.0 0.0 0.0 -9.00 -9.00 -9.0 -9.0 -9.0 0 -9", + "01002 S000001 00 1970 P 15 S S 100.0 0.0 0.0 0.0 -9.00 -9.00 -9.0 -9.0 -9.0 0 -9", + "01002 S000001 00 1970 " + ) + ) { + + resolver.addStream("test.dat", is); + + var parser = new VdypSpeciesParser().map("test.dat", resolver, controlMap); + + var stream = parser.get(); + + assertTrue(stream.hasNext(), "stream is empty"); + + var result = new ArrayList<>(stream.next()); // Array list makes it indexable so we can easily sample + // entries to test. Checking everything would be excessive. + + assertThat(result, hasSize(5)); + + assertThat( + result.get(0), + allOf( + hasProperty("polygonIdentifier", VdypMatchers.isPolyId("01002 S000001 00", 1970)), + hasProperty("layerType", is(LayerType.PRIMARY)), hasProperty("genus", is("B")) + ) + ); + + assertTrue(!stream.hasNext(), "stream is not empty"); + + assertThrows(NoSuchElementException.class, () -> stream.next()); + + } + } + + @Test + void testMultiplePoly() throws IOException, ResourceParseException { + var controlMap = TestUtils.loadControlMap(); + var resolver = new MockFileResolver("testResolver"); + + try ( + var is = TestUtils.makeInputStream( + "01002 S000001 00 1970 P 3 B B 100.0 0.0 0.0 0.0 -9.00 -9.00 -9.0 -9.0 -9.0 0 -9", + "01002 S000001 00 1970 P 4 C C 100.0 0.0 0.0 0.0 -9.00 -9.00 -9.0 -9.0 -9.0 0 -9", + "01002 S000001 00 1970 P 5 D D 100.0 0.0 0.0 0.0 35.00 35.30 55.0 54.0 1.0 1 13", + "01002 S000001 00 1970 P 8 H H 100.0 0.0 0.0 0.0 -9.00 -9.00 -9.0 -9.0 -9.0 0 -9", + "01002 S000001 00 1970 P 15 S S 100.0 0.0 0.0 0.0 -9.00 -9.00 -9.0 -9.0 -9.0 0 -9", + "01002 S000001 00 1970 ", + "01002 S000002 00 1970 P 3 B B 100.0 0.0 0.0 0.0 -9.00 -9.00 -9.0 -9.0 -9.0 0 -9", + "01002 S000002 00 1970 P 5 D D 100.0 0.0 0.0 0.0 -9.00 -9.00 -9.0 -9.0 -9.0 0 -9", + "01002 S000002 00 1970 P 8 H H 100.0 0.0 0.0 0.0 28.70 24.30 45.0 39.6 5.4 1 34", + "01002 S000002 00 1970 P 15 S S 100.0 0.0 0.0 0.0 -9.00 -9.00 -9.0 -9.0 -9.0 0 -9", + "01002 S000002 00 1970 V 3 B B 100.0 0.0 0.0 0.0 -9.00 -9.00 -9.0 -9.0 -9.0 0 -9", + "01002 S000002 00 1970 V 8 H H 100.0 0.0 0.0 0.0 16.70 26.20 105.0 97.9 7.1 1 -9", + "01002 S000002 00 1970 V 15 S S 100.0 0.0 0.0 0.0 -9.00 -9.00 -9.0 -9.0 -9.0 0 -9", + "01002 S000002 00 1970 " + + ) + ) { + + resolver.addStream("test.dat", is); + + var parser = new VdypSpeciesParser().map("test.dat", resolver, controlMap); + + var stream = parser.get(); + + assertTrue(stream.hasNext(), "stream is empty"); + + var result = new ArrayList<>(stream.next()); // Array list makes it indexable so we can easily sample + // entries to test. Checking everything would be excessive. + + assertThat(result, hasSize(5)); + + assertThat( + result.get(0), + allOf( + hasProperty("polygonIdentifier", VdypMatchers.isPolyId("01002 S000001 00", 1970)), + hasProperty("layerType", is(LayerType.PRIMARY)), hasProperty("genus", is("B")) + ) + ); + assertThat( + result.get(4), + allOf( + hasProperty("polygonIdentifier", VdypMatchers.isPolyId("01002 S000001 00", 1970)), + hasProperty("layerType", is(LayerType.PRIMARY)), hasProperty("genus", is("S")) + ) + ); + assertTrue(stream.hasNext(), "stream is empty"); + + result = new ArrayList<>(stream.next()); // Array list makes it indexable so we can easily sample + // entries to test. Checking everything would be excessive. + + assertThat(result, hasSize(7)); + + assertThat( + result.get(0), + allOf( + hasProperty("polygonIdentifier", VdypMatchers.isPolyId("01002 S000002 00", 1970)), + hasProperty("layerType", is(LayerType.PRIMARY)), hasProperty("genus", is("B")) + ) + ); + assertThat( + result.get(3), + allOf( + hasProperty("polygonIdentifier", VdypMatchers.isPolyId("01002 S000002 00", 1970)), + hasProperty("layerType", is(LayerType.PRIMARY)), hasProperty("genus", is("S")) + ) + ); + assertThat( + result.get(4), + allOf( + hasProperty("polygonIdentifier", VdypMatchers.isPolyId("01002 S000002 00", 1970)), + hasProperty("layerType", is(LayerType.VETERAN)), hasProperty("genus", is("B")) + ) + ); + assertThat( + result.get(6), + allOf( + hasProperty("polygonIdentifier", VdypMatchers.isPolyId("01002 S000002 00", 1970)), + hasProperty("layerType", is(LayerType.VETERAN)), hasProperty("genus", is("S")) + ) + ); + + assertTrue(!stream.hasNext(), "stream is not empty"); + + assertThrows(NoSuchElementException.class, () -> stream.next()); + + } + } + +} From 365f8c5ddac3e7e7745e103030547f0693c728ff Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 23 Oct 2024 12:18:10 -0700 Subject: [PATCH 15/22] Unit test for polygon parser --- .../io/parse/model/VdypPolygonParserTest.java | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonParserTest.java diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonParserTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonParserTest.java new file mode 100644 index 000000000..221d39d9a --- /dev/null +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonParserTest.java @@ -0,0 +1,139 @@ +package ca.bc.gov.nrs.vdyp.io.parse.model; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.NoSuchElementException; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; +import ca.bc.gov.nrs.vdyp.model.LayerType; +import ca.bc.gov.nrs.vdyp.model.UtilizationClass; +import ca.bc.gov.nrs.vdyp.test.MockFileResolver; +import ca.bc.gov.nrs.vdyp.test.TestUtils; +import ca.bc.gov.nrs.vdyp.test.VdypMatchers; + +import static ca.bc.gov.nrs.vdyp.test.VdypMatchers.*; + +class VdypPolygonParserTest { + + @Test + void testEmpty() throws IOException, ResourceParseException { + var controlMap = TestUtils.loadControlMap(); + var resolver = new MockFileResolver("testResolver"); + + try (var is = TestUtils.makeInputStream("")) { + + resolver.addStream("test.dat", is); + + var parser = new VdypPolygonParser().map("test.dat", resolver, controlMap); + + var stream = parser.get(); + + assertTrue(!stream.hasNext(), "stream is not empty"); + + assertThrows(NoSuchElementException.class, () -> stream.next()); + } + } + + @Test + void testOnePoly() throws IOException, ResourceParseException { + var controlMap = TestUtils.loadControlMap(); + var resolver = new MockFileResolver("testResolver"); + + try (var is = TestUtils.makeInputStream("01002 S000001 00 1970 CWH A 99 37 1 1")) { + + resolver.addStream("test.dat", is); + + var parser = new VdypPolygonParser().map("test.dat", resolver, controlMap); + + var stream = parser.get(); + + assertTrue(stream.hasNext(), "stream is empty"); + + var result = stream.next(); + + assertThat( + result, + allOf( + hasProperty("polygonIdentifier", isPolyId("01002 S000001 00", 1970)), + hasProperty("biogeoclimaticZone", hasProperty("alias", is("CWH"))) + ) + ); + + assertTrue(!stream.hasNext(), "stream is not empty"); + + assertThrows(NoSuchElementException.class, () -> stream.next()); + + } + } + + @Test + void testMultiplePoly() throws IOException, ResourceParseException { + var controlMap = TestUtils.loadControlMap(); + var resolver = new MockFileResolver("testResolver"); + + try ( + var is = TestUtils.makeInputStream( + "01002 S000001 00 1970 CWH A 99 37 1 1", + "01002 S000002 00 1970 CWH A 98 15 75 1", + "01002 S000003 00 1970 IDF A 99 15 75 1" + + ) + ) { + + resolver.addStream("test.dat", is); + + var parser = new VdypPolygonParser().map("test.dat", resolver, controlMap); + + var stream = parser.get(); + + assertTrue(stream.hasNext(), "stream is empty"); + + var result = stream.next(); + + assertThat( + result, + allOf( + hasProperty("polygonIdentifier", isPolyId("01002 S000001 00", 1970)), + hasProperty("biogeoclimaticZone", hasProperty("alias", is("CWH"))) + ) + ); + + assertTrue(stream.hasNext(), "stream is empty"); + + result = stream.next(); + + assertThat( + result, + allOf( + hasProperty("polygonIdentifier", isPolyId("01002 S000002 00", 1970)), + hasProperty("biogeoclimaticZone", hasProperty("alias", is("CWH"))) + ) + ); + + assertTrue(stream.hasNext(), "stream is empty"); + + result = stream.next(); + + assertThat( + result, + allOf( + hasProperty("polygonIdentifier", isPolyId("01002 S000003 00", 1970)), + hasProperty("biogeoclimaticZone", hasProperty("alias", is("IDF"))) + ) + ); + + assertTrue(!stream.hasNext(), "stream is not empty"); + + assertThrows(NoSuchElementException.class, () -> stream.next()); + + } + } + +} From d0c9d2231760c8cef1cf17eefc27cab5ca07eb18 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 23 Oct 2024 13:08:25 -0700 Subject: [PATCH 16/22] Refactor to eliminate duplication and branches that were reducing test coverage reporting --- .../io/parse/model/VdypSpeciesParser.java | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java index b61b100fa..31398a1a2 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java @@ -136,25 +136,17 @@ protected ValueOrMarker, EndOfRecord> convert(Map gdList = new ArrayList<>(); - Utils.ifBothPresent( - genusNameText0.filter(t -> genusDefinitionMap.contains(t)), percentGenus0, - (s, p) -> gdList.add(new Sp64Distribution(1, s, p)) - ); - - Utils.ifBothPresent( - genusNameText1.filter(t -> genusDefinitionMap.contains(t)), percentGenus1, - (s, p) -> gdList.add(new Sp64Distribution(2, s, p)) - ); - - Utils.ifBothPresent( - genusNameText2.filter(t -> genusDefinitionMap.contains(t)), percentGenus2, - (s, p) -> gdList.add(new Sp64Distribution(3, s, p)) - ); - - Utils.ifBothPresent( - genusNameText3.filter(t -> genusDefinitionMap.contains(t)), percentGenus3, - (s, p) -> gdList.add(new Sp64Distribution(4, s, p)) - ); + speciesDistribution(genusDefinitionMap, genusNameText0, percentGenus0, 1) + .ifPresent(gdList::add); + + speciesDistribution(genusDefinitionMap, genusNameText1, percentGenus1, 2) + .ifPresent(gdList::add); + + speciesDistribution(genusDefinitionMap, genusNameText2, percentGenus2, 3) + .ifPresent(gdList::add); + + speciesDistribution(genusDefinitionMap, genusNameText3, percentGenus3, 4) + .ifPresent(gdList::add); Sp64DistributionSet speciesDistributionSet = new Sp64DistributionSet(4, gdList); @@ -181,7 +173,7 @@ protected ValueOrMarker, EndOfRecord> convert(Map { siteBuilder.ageTotal(inferredTotalAge); siteBuilder.height(dominantHeight); @@ -196,6 +188,16 @@ protected ValueOrMarker, EndOfRecord> convert(Map speciesDistribution( + GenusDefinitionMap genusDefinitionMap, Optional genusNameText0, + Optional percentGenus0, int index + ) { + return Utils.mapBoth( + genusNameText0.filter(genusDefinitionMap::contains), percentGenus0, + (s, p) -> new Sp64Distribution(index, s, p) + ); + } }; return new GroupingStreamingParser, ValueOrMarker, EndOfRecord>>( From 999597d475739e38f89dd4d0c45c29ad0b6f6f1b Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 23 Oct 2024 13:12:51 -0700 Subject: [PATCH 17/22] Unit test for polygon ID parser --- .../model/VdypPolygonDescriptionParser.java | 4 + .../VdypPolygonDescriptionParserTest.java | 199 ++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonDescriptionParserTest.java diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonDescriptionParser.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonDescriptionParser.java index be1d07dfb..e81f91cb1 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonDescriptionParser.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonDescriptionParser.java @@ -33,6 +33,10 @@ public static PolygonIdentifier parse(String description) throws ResourceParseEx Integer year; String name; + if (description.length() != PolygonIdentifier.ID_LENGTH) { + throw new ResourceParseException("Polygon description " + description + " was not 25 characters long"); + } + if (matcher.matches() && matcher.group(2) != null) { year = Integer.parseInt(matcher.group(2)); name = matcher.group(1).trim(); diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonDescriptionParserTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonDescriptionParserTest.java new file mode 100644 index 000000000..6c510617d --- /dev/null +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonDescriptionParserTest.java @@ -0,0 +1,199 @@ +package ca.bc.gov.nrs.vdyp.io.parse.model; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.NoSuchElementException; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; +import ca.bc.gov.nrs.vdyp.model.LayerType; +import ca.bc.gov.nrs.vdyp.model.UtilizationClass; +import ca.bc.gov.nrs.vdyp.test.MockFileResolver; +import ca.bc.gov.nrs.vdyp.test.TestUtils; +import ca.bc.gov.nrs.vdyp.test.VdypMatchers; + +import static ca.bc.gov.nrs.vdyp.test.VdypMatchers.*; + +class VdypPolygonDescriptionParserTest { + + @Test + void testEmpty() throws IOException, ResourceParseException { + var controlMap = TestUtils.loadControlMap(); + var resolver = new MockFileResolver("testResolver"); + + try (var is = TestUtils.makeInputStream("")) { + + resolver.addStream("test.dat", is); + + var parser = new VdypPolygonParser().map("test.dat", resolver, controlMap); + + var stream = parser.get(); + + assertTrue(!stream.hasNext(), "stream is not empty"); + + assertThrows(NoSuchElementException.class, () -> stream.next()); + } + } + + @Test + void testOnePoly() throws IOException, ResourceParseException { + var controlMap = TestUtils.loadControlMap(); + var resolver = new MockFileResolver("testResolver"); + + try (var is = TestUtils.makeInputStream("01002 S000001 00 1970")) { + + resolver.addStream("test.dat", is); + + var parser = new VdypPolygonDescriptionParser().map("test.dat", resolver, controlMap); + + var stream = parser.get(); + + assertTrue(stream.hasNext(), "stream is empty"); + + var result = stream.next(); + + assertThat(result, isPolyId("01002 S000001 00", 1970)); + + assertTrue(!stream.hasNext(), "stream is not empty"); + + assertThrows(NoSuchElementException.class, () -> stream.next()); + + } + } + + @Test + void testMultiplePoly() throws IOException, ResourceParseException { + var controlMap = TestUtils.loadControlMap(); + var resolver = new MockFileResolver("testResolver"); + + try ( + var is = TestUtils.makeInputStream( + "01002 S000001 00 1970", "01002 S000002 00 1970", "01002 S000003 00 1973" + + ) + ) { + + resolver.addStream("test.dat", is); + + var parser = new VdypPolygonDescriptionParser().map("test.dat", resolver, controlMap); + + var stream = parser.get(); + + assertTrue(stream.hasNext(), "stream is empty"); + + var result = stream.next(); + + assertThat(result, isPolyId("01002 S000001 00", 1970)); + + assertTrue(stream.hasNext(), "stream is empty"); + + result = stream.next(); + + assertThat(result, isPolyId("01002 S000002 00", 1970)); + + assertTrue(stream.hasNext(), "stream is empty"); + + result = stream.next(); + + assertThat(result, isPolyId("01002 S000003 00", 1973)); + + assertTrue(!stream.hasNext(), "stream is not empty"); + + assertThrows(NoSuchElementException.class, () -> stream.next()); + + } + } + + @ParameterizedTest + @ValueSource( + strings = { "01002 S000001 00 XXXX", "01002 S000001 00 199X", "01002 S000001 00 999", + "01002 S000001 00 999 ", "01002 S000001 00 19 9", "01002 S000001 00 " } + ) + void testIDsWithoutAValidYear(String id) throws IOException, ResourceParseException { + var controlMap = TestUtils.loadControlMap(); + var resolver = new MockFileResolver("testResolver"); + + try (var is = TestUtils.makeInputStream(id)) { + + resolver.addStream("test.dat", is); + + var parser = new VdypPolygonDescriptionParser().map("test.dat", resolver, controlMap); + + var stream = parser.get(); + + assertTrue(stream.hasNext(), "stream is empty"); + + var ex = assertThrows(ResourceParseException.class, () -> stream.next()); + + } + + } + + @ParameterizedTest + @ValueSource( + strings = { "01002 S000001 00 2024", // short (24) + "01002 S000001 00 2024" // long (26) + } + ) + void testIdsWithWrongLength(String id) throws IOException, ResourceParseException { + var controlMap = TestUtils.loadControlMap(); + var resolver = new MockFileResolver("testResolver"); + + try (var is = TestUtils.makeInputStream(id)) { + + resolver.addStream("test.dat", is); + + var parser = new VdypPolygonDescriptionParser().map("test.dat", resolver, controlMap); + + var stream = parser.get(); + + assertTrue(stream.hasNext(), "stream is empty"); + + var ex = assertThrows(ResourceParseException.class, () -> stream.next()); + + } + + } + + @ParameterizedTest + @ValueSource( + strings = { "01002 S000001 00 XXXX", // bad year + "01002 S000001 00 2024", // short + "01002 S000001 00 2024" // long + } + ) + void testCanProgressToNextLineAfterError(String id) throws IOException, ResourceParseException { + var controlMap = TestUtils.loadControlMap(); + var resolver = new MockFileResolver("testResolver"); + + try (var is = TestUtils.makeInputStream(id, "01002 S000002 00 1970")) { + + resolver.addStream("test.dat", is); + + var parser = new VdypPolygonDescriptionParser().map("test.dat", resolver, controlMap); + + var stream = parser.get(); + + assertTrue(stream.hasNext(), "stream is empty"); + + var ex = assertThrows(ResourceParseException.class, () -> stream.next()); + + assertTrue(stream.hasNext(), "stream is empty"); + + var result = stream.next(); + + assertThat(result, isPolyId("01002 S000002 00", 1970)); + + } + + } + +} From 29ac9989c5b405d4ad47edfa216bede74b5355a2 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Thu, 24 Oct 2024 11:39:29 -0700 Subject: [PATCH 18/22] Unit test for processor base class --- .../gov/nrs/vdyp/application/Processor.java | 2 +- .../io/parse/model/VdypSpeciesParser.java | 50 ++++++++----- .../nrs/vdyp/application/ProcessorTest.java | 59 +++++++++++++++ .../io/parse/model/VdypSpeciesParserTest.java | 71 +++++++++++++++++++ 4 files changed, 163 insertions(+), 19 deletions(-) create mode 100644 lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/ProcessorTest.java diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java index 347373fde..a6053e727 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java @@ -69,7 +69,7 @@ public void run( Path controlFilePath = inputFileResolver.toPath(controlFileName).getParent(); FileSystemFileResolver relativeResolver = new FileSystemFileResolver(controlFilePath); - parser.parse(is, relativeResolver, controlMap); + controlMap = parser.parse(is, relativeResolver, controlMap); } } diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java index 31398a1a2..4a39b7249 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java @@ -51,6 +51,8 @@ public class VdypSpeciesParser implements ControlMapValueReplacer, EndOfRecord> LAYER_TYPE_EOR_BUILDER = new ValueOrMarker.Builder<>(); + @Override public ControlKey getControlKey() { return ControlKey.FORWARD_INPUT_VDYP_LAYER_BY_SPECIES; @@ -107,11 +109,8 @@ protected ValueOrMarker, EndOfRecord> convert(Map, EndOfRecord>) entry.get(LAYER_TYPE); - if (layerType == null) { - var builder = new ValueOrMarker.Builder, EndOfRecord>(); - layerType = builder.marker(EndOfRecord.END_OF_RECORD); - } + var layerType = (ValueOrMarker, EndOfRecord>) entry + .getOrDefault(LAYER_TYPE, LAYER_TYPE_EOR_BUILDER.marker(EndOfRecord.END_OF_RECORD)); var genusIndex = (Integer) entry.get(GENUS_INDEX); var optionalGenus = (Optional) entry.get(GENUS); var genusNameText0 = (Optional) entry.get(SPECIES_0); @@ -152,20 +151,10 @@ protected ValueOrMarker, EndOfRecord> convert(Map 0.0 && yearsToBreastHeight > 0.0) - iTotalAge = yearsAtBreastHeight + yearsToBreastHeight; - } else if (Float.isNaN(yearsToBreastHeight)) { - if (yearsAtBreastHeight > 0.0 && totalAge > yearsAtBreastHeight) - iYearsToBreastHeight = totalAge - yearsAtBreastHeight; - } + var inferredAges = inferAges(new Ages(totalAge, yearsAtBreastHeight, yearsToBreastHeight)); - var inferredTotalAge = iTotalAge; - var inferredYearsToBreastHeight = iYearsToBreastHeight; + var inferredTotalAge = inferredAges.totalAge; + var inferredYearsToBreastHeight = inferredAges.yearsAtBreastHeight; return VdypSpecies.build(speciesBuilder -> { speciesBuilder.sp64DistributionSet(speciesDistributionSet); @@ -224,6 +213,31 @@ protected boolean stop(ValueOrMarker, EndOfRecord> nextChi }; } + record Ages(float totalAge, float yearsAtBreastHeight, float yearsToBreastHeight) { + } + + /** + * Fills in NaN value for one of totalAge or yearsToBreastHeight if the other values are valid numbers + * + * @param givenAges + * @return An ages object with the NaN value filled in. + */ + static Ages inferAges(final Ages givenAges) { + var iTotalAge = givenAges.totalAge; + var iYearsToBreastHeight = givenAges.yearsToBreastHeight; + + // From VDYPGETS.FOR, lines 235 to 255. + if (Float.isNaN(givenAges.totalAge)) { + if (givenAges.yearsAtBreastHeight > 0.0 && givenAges.yearsToBreastHeight > 0.0) + iTotalAge = givenAges.yearsAtBreastHeight + givenAges.yearsToBreastHeight; + } else if (Float.isNaN(givenAges.yearsToBreastHeight)) { + if (givenAges.yearsAtBreastHeight > 0.0 && givenAges.totalAge > givenAges.yearsAtBreastHeight) + iYearsToBreastHeight = givenAges.totalAge - givenAges.yearsAtBreastHeight; + } + + return new Ages(iTotalAge, givenAges.yearsAtBreastHeight, iYearsToBreastHeight); + } + @Override public ValueParser getValueParser() { return FILENAME; diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/ProcessorTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/ProcessorTest.java new file mode 100644 index 000000000..a77521a37 --- /dev/null +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/ProcessorTest.java @@ -0,0 +1,59 @@ +package ca.bc.gov.nrs.vdyp.application; + +import java.io.IOException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; + +import org.easymock.EasyMock; +import org.junit.jupiter.api.Test; + +import ca.bc.gov.nrs.vdyp.io.FileResolver; +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.test.MockFileResolver; +import ca.bc.gov.nrs.vdyp.test.TestUtils; + +class ProcessorTest { + + @Test + void test() throws IOException, ResourceParseException, ProcessingException { + + Processor unit = EasyMock.partialMockBuilder(Processor.class).addMockedMethod("getControlFileParser") + .addMockedMethod("process", Set.class, Map.class, Optional.class, Predicate.class).createStrictMock(); + + BaseControlParser controlParser = EasyMock.createMock(BaseControlParser.class); + + var inputResolver = new MockFileResolver("input"); + var outputResolver = new MockFileResolver("output"); + + var is = TestUtils.makeInputStream("TEST"); + + inputResolver.addStream("test.ctr", is); + + var mockMap = new HashMap(); + + EasyMock.expect( + controlParser + .parse(EasyMock.same(is), EasyMock.anyObject(FileResolver.class), EasyMock.anyObject(Map.class)) + ).andStubReturn(mockMap); + + EasyMock.expect(unit.getControlFileParser()).andStubReturn(controlParser); + + unit.process( + EasyMock.eq(EnumSet.allOf(Pass.class)), EasyMock.same(mockMap), EasyMock.anyObject(Optional.class), + EasyMock.anyObject(Predicate.class) + ); + EasyMock.expectLastCall(); + + EasyMock.replay(unit, controlParser); + + unit.run(inputResolver, outputResolver, List.of("test.ctr"), EnumSet.allOf(Pass.class)); + + EasyMock.verify(unit, controlParser); + } +} diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java index 92ef93b0b..d2a039e30 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java @@ -8,9 +8,13 @@ import java.util.ArrayList; import java.util.NoSuchElementException; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypSpeciesParser.Ages; import ca.bc.gov.nrs.vdyp.model.LayerType; import ca.bc.gov.nrs.vdyp.model.UtilizationClass; import ca.bc.gov.nrs.vdyp.test.MockFileResolver; @@ -179,4 +183,71 @@ void testMultiplePoly() throws IOException, ResourceParseException { } } + @Nested + class InferAges { + + @Test + void testNoNan() { + var result = VdypSpeciesParser.inferAges(new Ages(60, 50, 10)); + assertThat(result, equalTo(new Ages(60, 50, 10))); // Leave as is + } + + @Test + void testTotalNaN() { + var result = VdypSpeciesParser.inferAges(new Ages(Float.NaN, 50, 10)); + assertThat(result, equalTo(new Ages(60, 50, 10))); // Fill in total + } + + @Test + void testYtbNaN() { + var result = VdypSpeciesParser.inferAges(new Ages(60, 50, Float.NaN)); + assertThat(result, equalTo(new Ages(60, 50, 10))); // Fill Years to Breast Height + } + + // TODO maybe implement this the same as the other two + @Test + void testYabNaN() { + var result = VdypSpeciesParser.inferAges(new Ages(60, Float.NaN, 10)); + assertThat(result, equalTo(new Ages(60, Float.NaN, 10))); // Leave as is + } + + // TODO maybe we should log a warning for these cases? + + @Test + void testDontAddUp() { + var result = VdypSpeciesParser.inferAges(new Ages(60, 50, 5)); + assertThat(result, equalTo(new Ages(60, 50, 5))); // Leave as is + } + + @Test + void testTotalAbndYabBothNaN() { + var result = VdypSpeciesParser.inferAges(new Ages(Float.NaN, Float.NaN, 10)); + assertThat(result, equalTo(new Ages(Float.NaN, Float.NaN, 10))); // Leave as is + } + + @Test + void testTotalNaNYtbZero() { + var result = VdypSpeciesParser.inferAges(new Ages(Float.NaN, 50, 0)); + assertThat(result, equalTo(new Ages(Float.NaN, 50, 0))); // Leave as is + } + + @Test + void testTotalNaNYabZero() { + var result = VdypSpeciesParser.inferAges(new Ages(Float.NaN, 0, 10)); + assertThat(result, equalTo(new Ages(Float.NaN, 0, 10))); // Leave as is + } + + @Test + void testYtbNaNTotalZero() { + var result = VdypSpeciesParser.inferAges(new Ages(0, 50, Float.NaN)); + assertThat(result, equalTo(new Ages(0, 50, Float.NaN))); // Leave as is + } + + @Test + void testYtbNaNYabZero() { + var result = VdypSpeciesParser.inferAges(new Ages(60, 0, Float.NaN)); + assertThat(result, equalTo(new Ages(60, 0, Float.NaN))); // Leave as is + } + + } } From 4adebb5c70a071a9fd583034915236a1ae792ef0 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 25 Oct 2024 13:04:52 -0700 Subject: [PATCH 19/22] Unit test for LayerProcessingState --- .../vdyp/back/BackProcessingEngineTest.java | 46 +--- .../application/LayerProcessingStateTest.java | 253 ++++++++++++++++++ .../ca/bc/gov/nrs/vdyp/test/TestUtils.java | 4 + .../ca/bc/gov/nrs/vdyp/test/VdypMatchers.java | 45 ++++ 4 files changed, 304 insertions(+), 44 deletions(-) create mode 100644 lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/LayerProcessingStateTest.java diff --git a/lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java b/lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java index 05be1a747..37f2552c5 100644 --- a/lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java +++ b/lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java @@ -1,5 +1,6 @@ package ca.bc.gov.nrs.vdyp.back; +import static ca.bc.gov.nrs.vdyp.test.VdypMatchers.compatibilityVariable; import static ca.bc.gov.nrs.vdyp.test.VdypMatchers.notPresent; import static ca.bc.gov.nrs.vdyp.test.VdypMatchers.present; import static org.hamcrest.MatcherAssert.assertThat; @@ -37,6 +38,7 @@ import ca.bc.gov.nrs.vdyp.model.VdypPolygon; import ca.bc.gov.nrs.vdyp.model.VolumeVariable; import ca.bc.gov.nrs.vdyp.test.TestUtils; +import ca.bc.gov.nrs.vdyp.test.VdypMatchers; class BackProcessingEngineTest { @@ -332,7 +334,6 @@ protected boolean matchesSafely(ComponentSizeLimits item, Description mismatchDe }; } - static Matcher backCV(String name, Object p1, Object p2, Matcher expected) { return compatibilityVariable(name, expected, BackProcessingState.class, p1, p2); } @@ -340,47 +341,4 @@ static Matcher backCV(String name, Object p1, Object p2, Ma static Matcher backCV(String name, Object p1, Matcher expected) { return compatibilityVariable(name, expected, BackProcessingState.class, p1); } - - static Matcher - compatibilityVariable(String name, Matcher expected, Class klazz, Object... params) { - return new TypeSafeDiagnosingMatcher<>(klazz) { - - @Override - public void describeTo(Description description) { - description.appendText(name).appendValueList("(", ", ", ") ", params); - description.appendDescriptionOf(expected); - } - - @Override - protected boolean matchesSafely(T item, Description mismatchDescription) { - Method method; - try { - method = klazz.getMethod( - name, - (Class[]) Arrays.stream(params) - .map(o -> o instanceof Integer ? Integer.TYPE : o.getClass()).toArray(Class[]::new) - ); - } catch (NoSuchMethodException e) { - mismatchDescription.appendText("Method " + name + " does not exist"); - return false; - } - - float result; - try { - result = (float) method.invoke(item, params); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - mismatchDescription.appendText(e.getMessage()); - return false; - } - - if (expected.matches(result)) { - return true; - } - - expected.describeMismatch(result, mismatchDescription); - return false; - } - - }; - } } diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/LayerProcessingStateTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/LayerProcessingStateTest.java new file mode 100644 index 000000000..b68e59e85 --- /dev/null +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/LayerProcessingStateTest.java @@ -0,0 +1,253 @@ +package ca.bc.gov.nrs.vdyp.application; + +import static ca.bc.gov.nrs.vdyp.test.VdypMatchers.compatibilityVariable; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import org.easymock.EasyMock; +import org.easymock.IMocksControl; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import ca.bc.gov.nrs.vdyp.controlmap.ResolvedControlMap; +import ca.bc.gov.nrs.vdyp.model.LayerType; +import ca.bc.gov.nrs.vdyp.model.MatrixMap2; +import ca.bc.gov.nrs.vdyp.model.MatrixMap2Impl; +import ca.bc.gov.nrs.vdyp.model.MatrixMap3; +import ca.bc.gov.nrs.vdyp.model.MatrixMap3Impl; +import ca.bc.gov.nrs.vdyp.model.UtilizationClass; +import ca.bc.gov.nrs.vdyp.model.UtilizationClassVariable; +import ca.bc.gov.nrs.vdyp.model.VdypLayer; +import ca.bc.gov.nrs.vdyp.model.VdypPolygon; +import ca.bc.gov.nrs.vdyp.model.VdypSpecies; +import ca.bc.gov.nrs.vdyp.model.VolumeVariable; +import ca.bc.gov.nrs.vdyp.processing_state.LayerProcessingState; +import ca.bc.gov.nrs.vdyp.processing_state.ProcessingState; +import ca.bc.gov.nrs.vdyp.test.TestUtils; + +class LayerProcessingStateTest { + + static class TestLayerProcessingState extends LayerProcessingState { + + protected TestLayerProcessingState( + ProcessingState ps, VdypPolygon polygon, + LayerType subjectLayerType + ) throws ProcessingException { + super(ps, polygon, subjectLayerType); + } + + @Override + protected Predicate getBankFilter() { + return spec -> true; + } + + @Override + protected void applyCompatibilityVariables(VdypSpecies species, int i) { + // TODO Auto-generated method stub + + } + + @Override + protected VdypLayer updateLayerFromBank() { + // TODO Auto-generated method stub + return null; + } + + } + + @Nested + class Constructor { + + @Test + void testConstructNoSpecies() throws ProcessingException { + var em = EasyMock.createStrictControl(); + ProcessingState parent = em + .createMock("parent", ProcessingState.class); + var polygon = VdypPolygon.build(pb -> { + pb.polygonIdentifier("Test", 2024); + pb.biogeoclimaticZone(TestUtils.mockBec()); + pb.forestInventoryZone("A"); + pb.percentAvailable(90f); + + pb.addLayer(lb -> { + lb.layerType(LayerType.PRIMARY); + + }); + }); + + em.replay(); + + var unit = new TestLayerProcessingState(parent, polygon, LayerType.PRIMARY); + + assertThat(unit, hasProperty("NSpecies", is(0))); + + em.verify(); + + } + + @Test + void testConstructOneSpecies() throws ProcessingException { + var em = EasyMock.createStrictControl(); + ProcessingState parent = em + .createMock("parent", ProcessingState.class); + var polygon = VdypPolygon.build(pb -> { + pb.polygonIdentifier("Test", 2024); + pb.biogeoclimaticZone(TestUtils.mockBec()); + pb.forestInventoryZone("A"); + pb.percentAvailable(90f); + + pb.addLayer(lb -> { + lb.layerType(LayerType.PRIMARY); + + lb.addSpecies(sb -> { + sb.genus("A", 1); + }); + }); + }); + + em.replay(); + + var unit = new TestLayerProcessingState(parent, polygon, LayerType.PRIMARY); + + assertThat(unit, hasProperty("NSpecies", is(1))); + + em.verify(); + + } + } + + @Nested + class SetCompatibilityVariables { + IMocksControl em; + LayerProcessingState unit; + + MatrixMap3[] cvVolume; + MatrixMap2[] cvBa; + MatrixMap2[] cvDq; + Map[] cvSm; + + @BeforeEach + @SuppressWarnings("unchecked") + void setup() throws ProcessingException { + var em = EasyMock.createStrictControl(); + ProcessingState parent = em + .createMock("parent", ProcessingState.class); + var polygon = VdypPolygon.build(pb -> { + pb.polygonIdentifier("Test", 2024); + pb.biogeoclimaticZone(TestUtils.mockBec()); + pb.forestInventoryZone("A"); + pb.percentAvailable(90f); + + pb.addLayer(lb -> { + lb.layerType(LayerType.PRIMARY); + + lb.addSpecies(sb -> { + sb.genus("A", 1); + }); + }); + }); + + em.replay(); + + unit = new TestLayerProcessingState(parent, polygon, LayerType.PRIMARY); + + cvVolume = new MatrixMap3[] { null, new MatrixMap3Impl( + List.of(UtilizationClass.values()), List.of(VolumeVariable.values()), List.of(LayerType.values()), + (uc, vv, lt) -> 11f + vv.ordinal() * 2f + uc.ordinal() * 3f + lt.ordinal() * 5f + ) }; + + cvBa = new MatrixMap2[] { null, + new MatrixMap2Impl( + List.of(UtilizationClass.values()), List.of(LayerType.values()), + (uc, lt) -> 13f + uc.ordinal() * 3f + lt.ordinal() * 5f + ) }; + + cvDq = new MatrixMap2[] { null, + new MatrixMap2Impl( + List.of(UtilizationClass.values()), List.of(LayerType.values()), + (uc, lt) -> 17f + uc.ordinal() * 3f + lt.ordinal() * 5f + ) }; + cvSm = new EnumMap[] { null, new EnumMap(UtilizationClassVariable.class) }; + + for (var uc : UtilizationClassVariable.values()) { + cvSm[1].put(uc, uc.ordinal() * 7f); + } + + } + + @Test + void testFailBeforeSet() throws ProcessingException { + assertThrows( + IllegalStateException.class, + () -> unit.getCVVolume(1, UtilizationClass.ALL, VolumeVariable.CLOSE_UTIL_VOL, LayerType.PRIMARY), + "getCVVolume" + ); + assertThrows( + IllegalStateException.class, () -> unit.getCVBasalArea(1, UtilizationClass.ALL, LayerType.PRIMARY), + "getCVBasalArea" + ); + assertThrows( + IllegalStateException.class, + () -> unit.getCVQuadraticMeanDiameter(1, UtilizationClass.ALL, LayerType.PRIMARY), + "getCVQuadraticMeanDiameter" + ); + assertThrows( + IllegalStateException.class, () -> unit.getCVSmall(1, UtilizationClassVariable.BASAL_AREA), + "getCVSmall" + ); + } + + @Test + void testSucceedAfterSet() throws ProcessingException { + unit.setCompatibilityVariableDetails(cvVolume, cvBa, cvDq, cvSm); + assertThat( + unit, + testCV( + "getCVVolume", 1, UtilizationClass.ALL, VolumeVariable.CLOSE_UTIL_VOL, LayerType.PRIMARY, + is(16f) + ) + ); + assertThat(unit, testCV("getCVBasalArea", 1, UtilizationClass.ALL, LayerType.PRIMARY, is(16f))); + assertThat( + unit, + testCV("getCVQuadraticMeanDiameter", 1, UtilizationClass.U125TO175, LayerType.PRIMARY, is(26f)) + ); + assertThat(unit, testCV("getCVSmall", 1, UtilizationClassVariable.QUAD_MEAN_DIAMETER, is(7f))); + } + + @Test + void testFailDoubleSet() throws ProcessingException { + unit.setCompatibilityVariableDetails(cvVolume, cvBa, cvDq, cvSm); + assertThrows( + IllegalStateException.class, () -> unit.setCompatibilityVariableDetails(cvVolume, cvBa, cvDq, cvSm) + ); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + Matcher> + testCV(String name, Object p1, Object p2, Object p3, Object p4, Matcher match) { + return (Matcher) compatibilityVariable(name, match, LayerProcessingState.class, p1, p2, p3, p4); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + Matcher> testCV(String name, Object p1, Object p2, Object p3, Matcher match) { + return (Matcher) compatibilityVariable(name, match, LayerProcessingState.class, p1, p2, p3); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + Matcher> testCV(String name, Object p1, Object p2, Matcher match) { + return (Matcher) compatibilityVariable(name, match, LayerProcessingState.class, p1, p2); + } + + } +} diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/test/TestUtils.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/test/TestUtils.java index 11491ef43..d63e90f91 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/test/TestUtils.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/test/TestUtils.java @@ -634,4 +634,8 @@ public static BiFunction> netDecayMap(i return layer.getSpecies().get(ids[0]); } + + public static BecDefinition mockBec() { + return new BecDefinition("T", Region.COASTAL, "Test"); + } } diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/test/VdypMatchers.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/test/VdypMatchers.java index 92c70aa13..ced962a41 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/test/VdypMatchers.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/test/VdypMatchers.java @@ -14,6 +14,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -693,4 +695,47 @@ protected boolean matchesSafely(Coefficients item, Description mismatchDescripti }; } + public static Matcher + compatibilityVariable(String name, Matcher expected, Class klazz, Object... params) { + return new TypeSafeDiagnosingMatcher<>(klazz) { + + @Override + public void describeTo(Description description) { + description.appendText(name).appendValueList("(", ", ", ") ", params); + description.appendDescriptionOf(expected); + } + + @Override + protected boolean matchesSafely(T item, Description mismatchDescription) { + Method method; + try { + method = klazz.getMethod( + name, + (Class[]) Arrays.stream(params) + .map(o -> o instanceof Integer ? Integer.TYPE : o.getClass()).toArray(Class[]::new) + ); + } catch (NoSuchMethodException e) { + mismatchDescription.appendText("Method " + name + " does not exist"); + return false; + } + + float result; + try { + result = (float) method.invoke(item, params); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + mismatchDescription.appendText(e.getMessage()); + return false; + } + + if (expected.matches(result)) { + return true; + } + + expected.describeMismatch(result, mismatchDescription); + return false; + } + + }; + } + } From 4ae7c0ef37f1e7c7088c38530d3e45a1412ed1ed Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 25 Oct 2024 13:31:44 -0700 Subject: [PATCH 20/22] Fix typo in age inference --- .../java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java | 1 + .../ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java b/lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java index 37f2552c5..fe741ae79 100644 --- a/lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java +++ b/lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java @@ -334,6 +334,7 @@ protected boolean matchesSafely(ComponentSizeLimits item, Description mismatchDe }; } + static Matcher backCV(String name, Object p1, Object p2, Matcher expected) { return compatibilityVariable(name, expected, BackProcessingState.class, p1, p2); } diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java index 4a39b7249..6e278577e 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java @@ -154,7 +154,7 @@ protected ValueOrMarker, EndOfRecord> convert(Map { speciesBuilder.sp64DistributionSet(speciesDistributionSet); From 96eeb56fc211f6e83bb1d76fc56b16371afa25fb Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Mon, 28 Oct 2024 11:01:48 -0700 Subject: [PATCH 21/22] Unit test for building a layer from a bank --- .../nrs/vdyp/processing_state/BankTest.java | 61 +++++++++++++++++-- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java index 89aac59c1..2cf7951bf 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java @@ -54,18 +54,22 @@ void before() throws IOException, ResourceParseException { lb.addSpecies(sb -> { sb.genus("B", controlMap); sb.baseArea(0.4f); + sb.percentGenus(10); }); lb.addSpecies(sb -> { sb.genus("C", controlMap); sb.baseArea(0.6f); + sb.percentGenus(10); }); lb.addSpecies(sb -> { sb.genus("D", controlMap); sb.baseArea(10f); + sb.percentGenus(10); }); lb.addSpecies(sb -> { sb.genus("H", controlMap); sb.baseArea(50f); + sb.percentGenus(60); sb.addSite(ib -> { ib.ageTotal(100); ib.yearsToBreastHeight(5); @@ -77,6 +81,7 @@ void before() throws IOException, ResourceParseException { lb.addSpecies(sb -> { sb.genus("S", controlMap); sb.baseArea(99.9f); + sb.percentGenus(10); sb.addSite(ib -> { ib.ageTotal(100); ib.yearsToBreastHeight(5); @@ -242,6 +247,46 @@ void testLayerUpdate() throws IOException, ResourceParseException, ProcessingExc verifyBankMatchesLayer(bank, pLayer); } + @Test + void testBuildLayerFromBank() throws IOException, ResourceParseException, ProcessingException { + + VdypLayer pLayer = polygon.getLayers().get(LayerType.PRIMARY); + assertThat(pLayer, notNullValue()); + + Bank bank = new Bank(pLayer, polygon.getBiogeoclimaticZone(), s -> true); + + pLayer = ProcessingTestUtils.normalizeLayer(pLayer); + + verifyBankMatchesLayer(bank, pLayer); + + var bankPart2 = new float[][][] { bank.loreyHeights, bank.basalAreas, bank.quadMeanDiameters, + bank.treesPerHectare, bank.wholeStemVolumes, bank.closeUtilizationVolumes, bank.cuVolumesMinusDecay, + bank.cuVolumesMinusDecayAndWastage }; + + var bankPart1 = new float[][] { bank.ageTotals, bank.dominantHeights, + // bank.percentagesOfForestedLand, + bank.siteIndices, bank.yearsAtBreastHeight + // bank.yearsToBreastHeight // + }; + + for (int i = 0; i < bankPart1.length; i++) { + for (int j = 0; j < bankPart1[i].length; j++) { + bankPart1[i][j] += 1; + } + } + for (int i = 0; i < bankPart2.length; i++) { + for (int j = 0; j < bankPart2[i].length; j++) { + for (int k = 0; k < bankPart2[i][j].length; k++) { + bankPart2[i][j][k] += 1; + } + } + } + + var result = bank.buildLayerFromBank(); + + verifyBankMatchesLayer(bank, result); + } + private void verifyBankMatchesLayer(Bank lps, VdypLayer layer) { List sortedSpIndices = layer.getSpecies().values().stream().map(s -> s.getGenusIndex()).sorted() @@ -293,11 +338,17 @@ private void verifyBankSpeciesMatchesSpecies(Bank bank, int index, VdypSpecies s assertThat(bank.speciesNames[index], is(species.getGenus())); species.getSite().ifPresentOrElse(site -> { - assertThat(bank.yearsAtBreastHeight[index], is(site.getYearsAtBreastHeight().get())); - assertThat(bank.ageTotals[index], is(site.getAgeTotal().get())); - assertThat(bank.dominantHeights[index], is(site.getHeight().get())); - assertThat(bank.siteIndices[index], is(site.getSiteIndex().get())); - assertThat(bank.yearsToBreastHeight[index], is(site.getYearsToBreastHeight().get())); + assertThat( + bank.yearsAtBreastHeight[index], + is(site.getYearsAtBreastHeight().orElse(VdypEntity.MISSING_FLOAT_VALUE)) + ); + assertThat(bank.ageTotals[index], is(site.getAgeTotal().orElse(VdypEntity.MISSING_FLOAT_VALUE))); + assertThat(bank.dominantHeights[index], is(site.getHeight().orElse(VdypEntity.MISSING_FLOAT_VALUE))); + assertThat(bank.siteIndices[index], is(site.getSiteIndex().orElse(VdypEntity.MISSING_FLOAT_VALUE))); + assertThat( + bank.yearsToBreastHeight[index], + is(site.getYearsToBreastHeight().orElse(VdypEntity.MISSING_FLOAT_VALUE)) + ); site.getSiteCurveNumber().ifPresentOrElse(scn -> { assertThat(bank.siteCurveNumbers[index], is(scn)); }, () -> { From 138043dc61278bb7efcda8de312a7c9876cad1a2 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Mon, 28 Oct 2024 11:54:16 -0700 Subject: [PATCH 22/22] Cleanign up warnings --- .../nrs/vdyp/back/BackProcessingEngineTest.java | 4 ---- .../application/VdypProcessingApplication.java | 4 +--- .../vdyp/io/parse/model/VdypUtilizationParser.java | 2 +- .../gov/nrs/vdyp/io/parse/value/ValueParser.java | 3 ++- .../bc/gov/nrs/vdyp/application/ProcessorTest.java | 1 + .../application/VdypProcessingApplicationTest.java | 8 ++++---- .../model/VdypPolygonDescriptionParserTest.java | 5 ----- .../vdyp/io/parse/model/VdypPolygonParserTest.java | 5 ----- .../vdyp/io/parse/model/VdypSpeciesParserTest.java | 3 --- .../bc/gov/nrs/vdyp/processing_state/BankTest.java | 3 --- .../LayerProcessingStateTest.java | 14 +++++++++----- .../nrs/vdyp/forward/test/ForwardTestUtils.java | 6 ------ 12 files changed, 18 insertions(+), 40 deletions(-) rename lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/{application => processing_state}/LayerProcessingStateTest.java (96%) diff --git a/lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java b/lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java index fe741ae79..d1693c1d4 100644 --- a/lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java +++ b/lib/vdyp-back/src/test/java/ca/bc/gov/nrs/vdyp/back/BackProcessingEngineTest.java @@ -6,9 +6,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; import java.util.EnumMap; import java.util.List; import java.util.Map; @@ -38,7 +35,6 @@ import ca.bc.gov.nrs.vdyp.model.VdypPolygon; import ca.bc.gov.nrs.vdyp.model.VolumeVariable; import ca.bc.gov.nrs.vdyp.test.TestUtils; -import ca.bc.gov.nrs.vdyp.test.VdypMatchers; class BackProcessingEngineTest { diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java index 0e700731a..1ec7999de 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplication.java @@ -74,8 +74,6 @@ public int run(final PrintStream os, final InputStream is, final String... args) processor.run(new FileSystemFileResolver(), new FileSystemFileResolver(), controlFileNames, getAllPasses()); - System.err.println("Blah"); - } catch (Exception ex) { logger.error("Error during processing", ex); return PROCESSING_ERROR_EXIT; @@ -90,7 +88,7 @@ public List getControlFileNamesFromUser(final PrintStream os, final Inpu List controlFileNames; os.print( MessageFormat - .format("Enter name of control file (or RETURN for {1}) or *name for both): ", defaultFilename) + .format("Enter name of control file (or RETURN for {0}) or *name for both): ", defaultFilename) ); controlFileNames = new ArrayList<>(); diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypUtilizationParser.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypUtilizationParser.java index d9857215c..57b5035c5 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypUtilizationParser.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypUtilizationParser.java @@ -64,7 +64,7 @@ public ControlKey getControlKey() { ) ).value(3, GENUS_INDEX, ValueParser.INTEGER) // .space(1) // - .value(2, GENUS, ControlledValueParser.optional(ValueParser.GENUS)) + .value(2, GENUS, ControlledValueParser.optional(ControlledValueParser.GENUS)) .value(3, UTILIZATION_CLASS_INDEX, ControlledValueParser.UTILIZATION_CLASS) .value(9, BASAL_AREA, ValueParser.FLOAT_WITH_DEFAULT) .value(9, LIVE_TREES_PER_HECTARE, ValueParser.FLOAT_WITH_DEFAULT) diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/value/ValueParser.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/value/ValueParser.java index 87b59fd6f..2753a7247 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/value/ValueParser.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/value/ValueParser.java @@ -1,5 +1,6 @@ package ca.bc.gov.nrs.vdyp.io.parse.value; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -71,7 +72,7 @@ static > ValueParser rangeSilentWithDefaulting( if (!defaultValue.equals(result) && (result.compareTo(max) > (includeMax ? 0 : -1) || result.compareTo(min) < (includeMin ? 0 : 1))) { return Optional.of( - String.format( + MessageFormat.format( "{} must be between {} ({}) and {} ({})", name, min, includeMin ? "inclusive" : "exclusive", max, includeMax ? "inclusive" : "exclusive" ) diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/ProcessorTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/ProcessorTest.java index a77521a37..a1bd20145 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/ProcessorTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/ProcessorTest.java @@ -20,6 +20,7 @@ class ProcessorTest { + @SuppressWarnings("unchecked") @Test void test() throws IOException, ResourceParseException, ProcessingException { diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java index 2ff829e0d..164a84580 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/VdypProcessingApplicationTest.java @@ -24,14 +24,14 @@ class VdypProcessingApplicationTest { @Nested class Run { - VdypProcessingApplication app; + VdypProcessingApplication app; Processor processor; @BeforeEach void init() { processor = EasyMock.createMock(Processor.class); - app = new VdypProcessingApplication() { + app = new VdypProcessingApplication() { @Override public String getDefaultControlFileName() { @@ -154,11 +154,11 @@ void testErrorWhileProcessing() throws Exception { @Nested class GetControlFileNamesFromUser { - VdypProcessingApplication app; + VdypProcessingApplication app; @BeforeEach void init() { - app = new VdypProcessingApplication() { + app = new VdypProcessingApplication() { @Override public String getDefaultControlFileName() { diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonDescriptionParserTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonDescriptionParserTest.java index 6c510617d..f6b8082f8 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonDescriptionParserTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonDescriptionParserTest.java @@ -5,20 +5,15 @@ import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; -import java.util.ArrayList; import java.util.NoSuchElementException; -import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; -import ca.bc.gov.nrs.vdyp.model.LayerType; -import ca.bc.gov.nrs.vdyp.model.UtilizationClass; import ca.bc.gov.nrs.vdyp.test.MockFileResolver; import ca.bc.gov.nrs.vdyp.test.TestUtils; -import ca.bc.gov.nrs.vdyp.test.VdypMatchers; import static ca.bc.gov.nrs.vdyp.test.VdypMatchers.*; diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonParserTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonParserTest.java index 221d39d9a..6e19d5d43 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonParserTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypPolygonParserTest.java @@ -5,18 +5,13 @@ import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; -import java.util.ArrayList; import java.util.NoSuchElementException; -import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; -import ca.bc.gov.nrs.vdyp.model.LayerType; -import ca.bc.gov.nrs.vdyp.model.UtilizationClass; import ca.bc.gov.nrs.vdyp.test.MockFileResolver; import ca.bc.gov.nrs.vdyp.test.TestUtils; -import ca.bc.gov.nrs.vdyp.test.VdypMatchers; import static ca.bc.gov.nrs.vdyp.test.VdypMatchers.*; diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java index d2a039e30..9a68c5fb3 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java @@ -8,15 +8,12 @@ import java.util.ArrayList; import java.util.NoSuchElementException; -import org.hamcrest.Matchers; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; import ca.bc.gov.nrs.vdyp.io.parse.model.VdypSpeciesParser.Ages; import ca.bc.gov.nrs.vdyp.model.LayerType; -import ca.bc.gov.nrs.vdyp.model.UtilizationClass; import ca.bc.gov.nrs.vdyp.test.MockFileResolver; import ca.bc.gov.nrs.vdyp.test.TestUtils; import ca.bc.gov.nrs.vdyp.test.VdypMatchers; diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java index 2cf7951bf..df05370dc 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/BankTest.java @@ -8,9 +8,6 @@ import java.util.List; import java.util.Map; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeDiagnosingMatcher; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/LayerProcessingStateTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/LayerProcessingStateTest.java similarity index 96% rename from lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/LayerProcessingStateTest.java rename to lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/LayerProcessingStateTest.java index b68e59e85..65dcbb3e8 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/LayerProcessingStateTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/processing_state/LayerProcessingStateTest.java @@ -1,4 +1,4 @@ -package ca.bc.gov.nrs.vdyp.application; +package ca.bc.gov.nrs.vdyp.processing_state; import static ca.bc.gov.nrs.vdyp.test.VdypMatchers.compatibilityVariable; import static org.hamcrest.MatcherAssert.assertThat; @@ -14,11 +14,12 @@ import org.easymock.EasyMock; import org.easymock.IMocksControl; import org.hamcrest.Matcher; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import ca.bc.gov.nrs.vdyp.application.ProcessingException; import ca.bc.gov.nrs.vdyp.controlmap.ResolvedControlMap; import ca.bc.gov.nrs.vdyp.model.LayerType; import ca.bc.gov.nrs.vdyp.model.MatrixMap2; @@ -31,8 +32,6 @@ import ca.bc.gov.nrs.vdyp.model.VdypPolygon; import ca.bc.gov.nrs.vdyp.model.VdypSpecies; import ca.bc.gov.nrs.vdyp.model.VolumeVariable; -import ca.bc.gov.nrs.vdyp.processing_state.LayerProcessingState; -import ca.bc.gov.nrs.vdyp.processing_state.ProcessingState; import ca.bc.gov.nrs.vdyp.test.TestUtils; class LayerProcessingStateTest { @@ -139,7 +138,7 @@ class SetCompatibilityVariables { @BeforeEach @SuppressWarnings("unchecked") void setup() throws ProcessingException { - var em = EasyMock.createStrictControl(); + em = EasyMock.createStrictControl(); ProcessingState parent = em .createMock("parent", ProcessingState.class); var polygon = VdypPolygon.build(pb -> { @@ -185,6 +184,11 @@ void setup() throws ProcessingException { } + @AfterEach + void cleanup() { + em.verify(); + } + @Test void testFailBeforeSet() throws ProcessingException { assertThrows( diff --git a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/test/ForwardTestUtils.java b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/test/ForwardTestUtils.java index e02234555..e4b248c8d 100644 --- a/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/test/ForwardTestUtils.java +++ b/lib/vdyp-forward/src/test/java/ca/bc/gov/nrs/vdyp/forward/test/ForwardTestUtils.java @@ -3,14 +3,12 @@ import java.io.IOException; import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.function.BiFunction; import org.opentest4j.AssertionFailedError; import ca.bc.gov.nrs.vdyp.application.VdypApplicationIdentifier; -import ca.bc.gov.nrs.vdyp.common_calculators.BaseAreaTreeDensityDiameter; import ca.bc.gov.nrs.vdyp.forward.ForwardControlParser; import ca.bc.gov.nrs.vdyp.forward.ForwardProcessingState; import ca.bc.gov.nrs.vdyp.forward.model.ControlVariable; @@ -19,10 +17,6 @@ import ca.bc.gov.nrs.vdyp.io.parse.control.BaseControlParser; import ca.bc.gov.nrs.vdyp.io.parse.value.ValueParseException; import ca.bc.gov.nrs.vdyp.model.Region; -import ca.bc.gov.nrs.vdyp.model.UtilizationClass; -import ca.bc.gov.nrs.vdyp.model.UtilizationVector; -import ca.bc.gov.nrs.vdyp.model.VdypLayer; -import ca.bc.gov.nrs.vdyp.model.VdypSpecies; import ca.bc.gov.nrs.vdyp.test.TestUtils; public class ForwardTestUtils {