Thursday, October 24, 2024

Spatial Planning - Operational Feasibility in Map Data

Introduction

In this article, we look at the issue of map data with an eye toward operational feasibility. If GIS map preparation is sloppy, you can end up with duplicates, overlapping polygons and/or gaps in the coverage. Moreover, we can make the situation even worse when we intersect these layers in GIS or try to group stands together using analysis area control.

Intersection Considerations

Best practices suggest that GIS layers represent similar types of information, such as stand boundaries, soil series or harvest units. A simple intersection of these types of information can result in a very large number of new polygons due to near-coincident lines. This can be a problem if available yield tables only represent a portion of those new combinations of attributes. So how do you avoid this? 


A harvest unit may incorporate multiple stands.



Stands that can be harvested together have similar ages. Outliers are not operable.

Coverages are hierarchical in nature. Property boundaries, surveyed features like right-of-ways, etc. should be considered inviolate, and trump any other coverage. A clipping function should be applied to ensure that only stands within the boundary extents are counted.

Stand boundaries have an inherent fuzziness to them such that sliver removal procedures should not cause too much change overall. For soil series, I recommend that instead of a topological polygon intersection, clients perform a spatial overlay, assigning the stand polygons the majority soil type within each boundary. That way, there is only one soil type per stand, with consistency in site quality and productivity. In other words, only one yield table per stand.

Finally, consider what I call my "1%/1000 acre rule": if a development type is less than 1% of the total land base or less than 1000 acres (whichever is smaller), it is too small for consideration in the model. For example, if you are modeling a property of 500,000 acres, does it make sense to spend resources on yield table generation for 150 acres (0.03%)? Even if it contains the most valuable timber, its contribution to the objective function is going to be negligible. For small projects, the thresholds may not work as-is, but the concept still holds. You can, of course, choose different thresholds, but you shouldn't just accept every possible permutation of thematic attributes that is presented to you.

Harvest Blocking Considerations

For harvest unit boundaries, focus on retaining stand boundaries as much as possible when you do layout. An arbitrary straight line between two similar stands often will not cause problems. However, if the stands are very different in age, the solver may find it difficult to schedule the resulting harvest unit due to operability criteria. Why would this happen?

If you are using Woodstock's analysis area control feature (AAControl), you need to be aware that the a harvest unit or block can only be assigned a single action (or prescription, if using Regimes). While the scheduling is at the block level, operability remains at the DevType level. That means if you combine stands where the operability conditions for each member polygon do not overlap (due to age or activity differences), the entire unit is never feasible to harvest. Some examples, you ask? How about when you include an area of reforestation in a unit that should be harvested? Or you combine two stands for commercial thinning where one portion must be fertilized and another does not?

A Software Fix for Sloppy Work

When you set up analysis area units (AAUnits) in Woodstock, you want the model to choose a single activity for the entire unit, preferably all in one planning period. Early on when clients tried AAControl, a flurry of tech-support calls came in complaining that units were never being cut. 

The culprit was sloppy layout, with incompatible stands grouped together. The solution was a software fix where units can be cut without 100% of the area treated. With this new approach, the units were scheduled but some of the areas within those units were not harvested. Ultimately, Remsoft set the default threshold for operability of AAUnits at 20% of the total area. Users can change that with higher or lower limits up to 100%. 

Frankly, I think this default is way too low. Sure, the sloppy units don’t create tech support calls anymore, but if you only harvest a minor fraction of a unit, what is the point of using AAControl? Worse, I don’t recall reading anything about what happens to the stands that were not scheduled. So I decided to investigate this operational feasibility issue for myself.

How to Control Operability of AAUnits

You activate AAControl and specify the threshold for operability of harvest units in Woodstock in the Control Section.

*AACONTROL ON ; minimum operable area is 20% of the unit are
*AACONTROL ON aCC ; schedule only the aCC action using AAControl
*AACONTROL ON 99% ; minimum operable area is 99% of the unit area

Remember, the default operability threshold is 20%. If you specify a threshold of 100%, all member polygons must be operable at the same time for Woodstock to create a decision variable for the unit. However, if some part of the unit is always inoperable, the unit will never be harvested. Conversely, if you accept the default, at least 20% of the area of a unit must be operable in order for Woodstock to generate a harvest choice for it. But that choice only includes the operable stands! The inoperable ones are omitted if that choice is part of the optimal solution. Look at the schedule section for omitted DevTypes:

  0.017831 aCC 4 _Existing _RX(rxV) |Aaunit:U343| ;B982.2 100.0%  
 31.863753 aCC 4 _Existing _RX(rxV) |Aaunit:U343| ;B982.6 100.0%    
  0.034987 aCC 4 _Existing _RX(rxV) |Aaunit:U343| ;B982.3 100.0%     
 68.775675 aCC 4 _Existing _RX(rxV) |Aaunit:U343| ;B982.4 100.0%     
  1.688875 aCC 4 _Existing _RX(rxV) |Aaunit:U343| ;B982.7 100.0%     
  5.554339 aCC 4 _Existing _RX(rxV) |Aaunit:U343| ;B982.5 100.0%      
  0        aCC 4 _Existing _RX(rxV) |Aaunit:U343| ;B982.1 100.0% Area = 0.77953                                                                 not operable in 4

The last record shows that 0.77953 acres of unit U343 is not operable in period 4, so it was not harvested, even though 100% of it was scheduled as part of unit U343!

What Happens to the Inoperable Parts?

The example above comes from a solution where I set the AAControl threshold to 99%. Since the unit is larger than 100 acres and the inoperable portion is less than 1 acre, clearly the unit meets the threshold for operability.

The inoperable areas are part of a larger development type (DevTYpe) that is split between two harvest units (U343 and U338). If we scan for the inoperable DevType acres later in the harvest schedule, we only find the acres in unit U338, not the residual from U343. The residual part of U343 never appears in the schedule section again. Note, this is not due to a constraint where only harvest units count. Also, I cannot say for sure that this happens in every model, but I have seen this behavior many times over the years.

If the number of acres that remain unscheduled is small, in terms of operational feasibility, it may not be a source of concern. For example, I’ve seen lots of spatial data with small fragments of older stands scattered among large areas of recent clearcuts, so it isn’t uncommon. However, at some point you do need to harvest those areas. A model that continues to produce unharvested fragments may be problematic from an operational feasibility standpoint.

How to Avoid “Forgotten Areas”

We just saw that including stands of very different ages can be problematic for scheduling final harvests. Clearly, it is best to group similar stands together in harvest units. If that is not possible, at least grow them all long enough that the youngest and oldest cohorts are both within the operability window. It may make perfect sense to hold an old stand fragment well beyond its normal rotation for operational feasibility. To make that happen, the old stand must be a significant part of the harvest unit, and you must also set a high operability threshold.

However, similar issues can arise with commercial thinning even if the ages are similar. For example, suppose that you generated thinned yield tables with defined age (14-17) and volume removal (> 50 tons/ac) requirements. The first stand is currently 15 years old but doesn’t reach the minimum volume bar until age 17. The second stand is currently 16 years old and is OK to thin immediately. But you created a unit that includes some of both stands. Now, when scheduling a thinning, there is no operability window to treat both stands simultaneously: by the time the first stand is operable in 2 years (age 17), the other stand will be too old (age 18).

17 109.37 rCT 2 _Existing _RX(rxTH) |Aaunit:AA099| ;B82.3 100.0%
16   0    rCT 2 _Existing _RX(rxTH) |Aaunit:AA099| ;B82.2 100.0% Area = 36.8500
                                                              not operable in 2

What's The Solution? Don’t Be Sloppy!

More care and attention to operability windows can avoid many problems. Don’t hardwire minimum volume limits when generating yield tables. What do I mean by this? Suppose you are generating yield tables for stands thinned from below to some basal area limit and you only generate a yield table if at least 50 tons/ac is produced. That works fine for harvesting stands, but when you use AAControl, that might cause some of your units to no longer be viable.

Instead, generate the tables accepting whatever volume results from the thinning. You can always add a minimum volume requirement to the *OPERABLE statement in Woodstock to avoid low-volume thinning. But, if there is no yield table for the low-volume stands, you can’t create one after the fact. Also, don’t set the threshold too high! Although the DevTypes in the unit determine the operability for an AAUnit, another portion of the unit can raise the average volume above the threshold for operational feasibility.


Magenta-colored stands do not meet operability threshold

For example, in the figure above, the magenta-colored polygons in the unit shown do not meet the 20 MBF/ac threshold desired. However, the blue areas exceed the bar by a significant amount. In fact, the overall average for the unit easily exceeds 20 MBF/ac. So, rather than set the minimum volume needed to 20 MBF/ac, maybe set it at 15 so the low volume portions of the block can still be harvested. You can still apply a constraint requiring at least 20MBF/ac harvested on average.

Suggestions for Better Operational Feasibility

Be careful with GIS overlays!

  • Avoid the creation of near-coincident lines and slivers.
  • Use spatial overlays to assign soil attributes to stand polygons.
  • Follow stand boundaries as much as possible when delineating harvest units.
  • Split stands only when necessary and into significant portions, not slivers.

Review proposed units to make sure they make sense!

  • Don’t combine incompatible stands within the same unit (age 5 + age 200 is not a good combo)
  • Provide yield tables and timing choices for member DevTypes that overlap in operability.
  • Remember that DevType operability determines unit operability, not the weighted average of the unit. Choose appropriate thresholds.
  • Don’t accept the Woodstock default of 20% for AAControl! If you can't harvest the majority of a harvest block, then the block is poorly laid out. Fix it!

Having troubles with spatial planning? Contact Me!

If you are concerned about the operational feasibility of your harvest planning models, give me a shout. We can schedule a review and work on some improvements that will provide more confidence in your results. 

Tuesday, October 1, 2024

Hide & Seek In Woodstock

Introduction

Most times, Woodstock modelers are working to report on outcomes and activities. But there are also times when you don't want to see something. Luckily, the syntax provides you with a few different keywords that can do this for you. First, we'll cover a few that have been around a long time, and a new one that was introduced in the latest release. As always, you should understand what's going on when you deploy a Woodstock keyword before using it in an analysis.

*EXCLUDE Keyword

The *EXCLUDE keyword can be applied in both the OPTIMIZE and AREAS sections, but the way the work is quite different in each section. Let's go over the syntax and rules for each

Optimize Section

The keyword tells the interpreter not to generate decision variables for the specified action(s) in the specified period(s). You might want to do this for a few reasons.

  • Resources needed to perform the action are unavailable.
  • A policy directive expires after a few years.
  • A pre-planned schedule (*LPSchedule) already includes enough acres of the specific treatment and you do not want to do more.

After *EXCLUDE, on the next line you type the action code and the period(s) in which you do not want the action to appear.

*EXCLUDE
aCC 1..5        ; considered in periods 6 onward
aTH 1.._LENGTH  ; never considered

The interpreter will not generate any decision variables for the clearcut action aCC in the first 5 periods. However, it will generate decision variables for actions in the LPSCHEDULE, regardless of any directives to exclude actions. The requirement for LPSCHEDULE actions to be implemented always trumps the *EXCLUDE statement.

Although you can write constraints to force the area treated to zero, most of the time using constraints like this is undesirable. The biggest problem is that the interpreter generates decision variables that not needed, and this inflates size of the LP matrix (i.e., making it harder to solve). Using the *EXCLUDE keyword makes it obvious that there will be no outputs related to the action. 

On the other hand, if you're trying to show the potential value of an action to a skeptical client, it may be better to use the constraints because there will be shadow prices. If the shadow price is zero, that indicates that the action provides no additional value. Otherwise, non-zero shadow prices would indicate the potential for a better solution.

Areas Section

The keyword tells the interpreter not to consider portions of the forest for a specified period of time. Again, you might want to do this for a number of reasons.

  • There are leases that expire in the near term.
  • You are considering a land acquisition and want to compare the estate NPV with and without the acquisition
  • Your agency is evaluating policy options that affect public land management.

Again, the syntax is easy. The *EXCLUDE keyword must appear before any area records. Below the keyword, you write full development type masks to represent areas you want excluded. Clearly, the easiest way to achieve this is through a particular theme and set of thematic attributes (e.g., an ownership them and attributes separating fee simple from leases). These also must appear before any area records that are prefaced by A.

*EXCLUDE
 ? ? ? ? TL2022 ? ? ? ? 3.._LENGTH ; timber lease expires in 2022
 ? ? ? ? TL2023 ? ? ? ? 4.._LENGTH ; timber lease expires in 2023
 ? ? ? ? TL2024 ? ? ? ? 5.._LENGTH ; timber lease expires in 2024
*A ABC 1023 PL PE FEE SI80 TM N E3000 37 124.32 ; 2 polygons
... 

Don't be misled, however. The excluded areas do not really disappear – if they did, the model would be infeasible because initial area constraints would be violated. Instead, the excluded areas are transferred to an internal pool of acres that are cannot be accessed by the user. They are not eligible for treatments nor are they reported in inventory measures.

*COMPRESSTIME Keyword

The *COMPRESSTIME keyword is a switch used in the CONTROL section. Its purpose is to limit the number of decision variables generated by the LP matrix. 

*COMPRESSTIME 41,2

I've found that a lot of people don't really understand how it works. Here are some rules:

  • Timing choice pruning commences in the first period (x) following the *COMPRESSTIME keyword, and continues until the end of the planning horizon.
  • The number of timing choices skipped (y) is specified after the starting period of *COMPRESTIME. 
  • It only applies to future development types that have undergone at least one transition. If your model has too many timing choices in existing DevTypes, *COMPRESSTIME will have no effect.
  • If the action has only one or two timing-choices per period, *COMPRESSTIME has no effect. The first and last timing choices are always generated.

I'm not a big fan of this feature because it potentially wastes a lot of time for biometricians. Imagine generating a ton of regen yield tables for commercial thinning for ages 10-24 in one-year increments. That's a LOT of yield tables, and if you're using *COMPRESSTIME, up to half of them are not being used. Consider the number of timing choices BEFORE commencing yield generation!

The New Not (!) Operator in the LANDSCAPE section

Basic landscape attributes and aggregate attributes provide a very flexible system for identifying development types, from very specific to very general. However, it has always been somewhat cumbersome to report on the "everyone but X" category if there are lots of attributes in the theme.

*THEME 5-Lease
 TL2024
 TL2025
 TL2026
 ...
 TL2055
 FEE
*AGGREGATE LEASES
 TL2024 TL2025 TL2026 ... TL2055
*AGGREGATE NO2024 ; everyone but 2024
 TL2025 TL2026 ... TL2055

The 2024 release of Woodstock now allows you to use a ! operator in a DevType mask in place of an aggregate. For example, suppose we want to report on inventory in unexpired leases in period 1. Previously we would need to use an aggregate like NO2024 shown above. In the new release, we don't need an aggregate and instead we write this:

*OUTPUT oiNo2024
*SOURCE ? ? ? ? !TL2024 ? ? ? ? _INVENT yiMBF

Discussion

Your action definitions are not affected by *EXCLUDE; the interpreter still processes the code. It is NOT the same as if you commented out the action specifications entirely. If you remove the action specifications, the interpreter will still see the code referenced in the TRANSITIONS and OUTPUTS sections, and will generate errors.

On the other hand, when you exclude development types from the AREAS section for the entire planning horizon, the effect is the same as if you commented out area records entirely. If you exclude area as a way to preclude harvesting, be careful! Any output derived from the excluded area will be zero, including outputs like habitat or hydrological maturity. Constraints that depend on the ratio of these types of habitats may no longer be feasible once the area is excluded. Let's consider an example.

Suppose you want to evaluate reallocations of land from one management emphasis to another. It is better to recognize those options within a landscape theme. Define actions that are restricted to one management emphasis. Evaluate the impact of reallocation by characterizing your forest with and without the new designation. If needed, create separate AREAS sections for each and run the analysis with scenarios.

Is Woodstock Syntax Eluding You? Contact Me!

If you’d like a new set of eyes to look over your model(s), give me a shout. I'm happy to review your model, and if needed, we can conduct an on-site training review with your team.

Tuesday, August 13, 2024

Benchmarking - A Method for Determining Appropriate Constraint Bounds

Introduction

Whenever I train new analysts in harvest scheduling and optimization, I make a point to include the basic assumptions of linear programming. The beauty of LP is that if a feasible solution exists, the solver will provide an optimal solution. One of the key points I make is that constraints will always cost you in terms of objective function value except in rare situations where the cost is zero. In other words, you can't constraint your way to an improved objective function value. So, you would think that people confronted with difficult policy constraints would spend the time to determine reasonable bounds before plowing ahead with analysis, and, dog forbid, employing goal formulations to avoid infeasibility. A recent project has taught me that I have more to teach.

What Is Possible?

Generally, allocations to different management emphases are more typical of public land management than private, but I have seen allocations to carbon projects or conservation easements handled similarly. My point is, however, that you should know what your forest is capable of. For example, if you have identified 3000 acres of land suitable for old growth designation, you should expect an infeasible solution if you constrain the model to allocate 4000 acres. That is patently infeasible (aka obvious). Yet, I've had that situation arise more than once in my career. But what if you're not able to explicitly identify qualifying stands, and you have more than one metric to consider (e.g., critical species, hydrological maturity, and so forth)? That is when you need to consider benchmarking.

Know Your Bounds

As a graduate student, I was very fortunate to attend a seminar given by Larry Davis from UC Berkeley where he introduced us to the concept of benchmarking. His opinion was that models can only go so far and that the final determination for a management plan was political rather than analytical. Models can only provide the boundaries within which a viable solution must exist. For example, suppose we have to consider timber production, late seral birds and bird hunting opportunities. Co-production of these metrics is possible to a degree, but how much of each is possible? Davis suggested that 3 model runs, each maximizing one of the metrics, provides critical information. Clearly, production of each metric can't be increased beyond the value of the respective objective function value. However, the lowest values of each metric in the non-optimized runs provide useful floor values as well. By taking the highest and lowest values of the three metrics, you now have a bounded space that can guide constraints in your analysis. For example, metric DIV never exceeds 4.5, and at worst, it never falls below 1.75. 


DIV1 - MAXMIN Objective


DEN2 - MAXMIN Objective


WFH3 - MAXMIN Objective

Depending on the number of metrics you need to consider, a benchmark exercise may require a lot matrix generations and solution time. Luckily, there is a way to speed things up by reducing the number of matrix generations.

Smart Benchmarking

In a benchmarking exercise, the idea is to find the biological capacity of the forest to produce the output of interest. If constraints are necessary (e.g., budgetary or strict policy constraints), they are imposed regardless of the objective function. And if the constraints are always the same, you can declare multiple objective functions and generate the matrix exactly once.

In the following example, I have 9 biodiversity indices to consider. I used a MAXMIN formulation because I thought it important to avoid solutions where the worst outcomes for each metric are crammed into one or two periods so that the rest of the periods appear better. (A more technical discussion of this phenomenon is presented here.) The only constraints are the need to cover any management costs from timber revenues (cash-flow positivity) and a requirement to reforest any backlog. 

*OBJECTIVE
 _MAXMIN oidxDIV 1.._LENGTH
 _MAXMIN oidxDEN 1.._LENGTH
 _MAXMIN oidxWFH 1.._LENGTH
 _MAXMIN oidxBEE 1.._LENGTH
 _MAXMIN oidxESB 1.._LENGTH
 _MAXMIN oidxLSB 1.._LENGTH
 _MAXMIN oidxRTV 1.._LENGTH
 _MAXMIN oidxAMP 1.._LENGTH
 _MAXMIN oidxUNG 1.._LENGTH
*CONSTRAINTS
 orHARV - ocHARV - ocSILV - ocFIXD >= 0 1.._LENGTH ;cash-flow +ve
 oaNULL <= 0 1 ;plant backlog

If you use the MOSEK solver, Woodstock will present a dialog after matrix generation where you can choose which objective to optimize. Once the solution has been written, simply create a scenario from the base model to store the results. Go back to your base model, select from the menu Run | Run Model Bat file, and when the dialog appears, choose a different objective. Continue creating scenarios and rerunning the batch file until all the benchmarks have been run. Usually, I wait until all the solutions are obtained before executing the schedules because I can do that automatically using the Run | Run Scenarios menu option. Once your scenarios have all been executed, use the Scenario Compare feature to determine highest and lowest values for each metric.

Other solvers may not support multiple objective functions directly. In this case, you can edit the MPS file that Woodstock generated to change the objective function ID on the first line from OBJ1MIN to OBJ2MIN:

NAME    RSPS 2023.2.1 (64 Bit)                              
ROWS
  N OBJ2MIN
  N OBJ1MIN
  N OBJ3MIN
  N OBJ4MIN
  N OBJ5MIN
  N OBJ6MIN
  N OBJ7MIN
  N OBJ8MIN
  N OBJ9MIN

Save the change but don't close the file. Use Run | Run Model Bat file as before and when complete save the scenario. You will need to change the objective function between each solution but the edit is fast and certainly quicker than generating a new matrix each time. Also, note that Woodstock limits you to 9 objective functions due to the naming convention of the MPS file.

Summary

This approach is useful for any analyst tasked with benchmarking, but it is particularly important for public agencies issuing policy requirements. Don't impose constraints without knowing whether they are technically feasible! You should be able to demonstrate how that it can be done using real-world data.

Looking For More Efficicency Ideas? Contact Me!

Give me a shout if you'd like to learn more about benchmarking analyses or any other modeling difficulties you may be having. No one has more real-world experience with Woodstock than me.

Thursday, June 20, 2024

The 3 Types of Woodstock Yield Tables (plus one)

Introduction

Currently, I'm working on a strategic planning model for a research forest in the U.S. Forests held by land-grant universities is subject to scrutiny from many interested parties: faculty, students, neighboring landowners, etc. In this case, we need to recognize ongoing research projects, recreational use, biodiversity, fire resilience and other metrics. Representing these metrics in the model required the use of all Woodstock yield types, so I thought this would be a good opportunity to review them with you.

Three Types of Yield Tables

Anyone who has taken Remsoft’s Modeling Fundamentals course was taught the three types of yield tables: age-dependent, time-dependent and complex. Let’s review each one.

Age-Dependent Yields

Yield tables that represent the development of stands over time are usually age-dependent. For a given age, the yield table shows things like product volume, diameter, dominant height, basal area, and other stand parameters. The yield table header starts with *Y followed by the development type mask.

*Y ? ? ? ? ? ? ? ? EX ? 00 00 ? ? ? ? 010111 010111  
_AGE yiTPA  yiBA yiBAO yxTHT yxQMD yxSDI yxCCF   yiTCF
   6   374   135    23    57   8.1   268   192   3.075
   7   359   153    26    67   8.8   295   209   3.995
   8   345   171    30    73   9.5   320   226   4.936
   9   332   189    34    80  10.2   343   241   5.899
  10   319   205    38    87  10.8   364   255   6.843

Of course, this assumes that you are practicing even-aged management, and that you know the age of your stands fairly precisely. Otherwise, you should be using a time-dependent yield table.

Time-Dependent Yields

Yield tables that represent values that depend on planning periods rather than age are time-dependent. Prices and costs that are expected to change over time (in real terms) are associated with particular planning periods, and the yield header starts with *YT followed by the development type mask.

*YT ? ? ? ? ? ? ? ? ? ? ? ? ? ? CA ? ? ?
y$CC 1 327
y$UI 1 377
y$UG 1 377
y$UV 1 285
y$OR 1 388

*YT ? ? ? ? ? ? ? ? ? ? ? ? ? ? GR ? ? ?
y$CC 1 189
y$UI 1 280
y$UG 1 222
y$UV 1 195
y$OR 1 388

Complex Yields

A complex yield table is really just a way to created new yield components based on previously-defined yield components. For example, you can sum individual product volumes to produce a total volume coefficient. Complex yield headers start with *YC.

Woodstock provides a number of built-in functions to make complex yields easier to define and to speed up processing. For example, I can define a complex yield coefficient that sums all volume coefficients (MBF/ac) that occur in the same period. I can also define cost coefficients that occur in the same period that depend on harvest volume ($/MBF). If I use this method to trigger total costs, I can reduce the matrix generation time. In theory, I should also be able to define a harvest cost coefficient by multiplying product harvest volume by logging cost.

*YC ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
yiDF _SUM(yiDF0,yiDF4,yiDF8,yiDF2,yiDF3)
ycCC _MULTIPLY(yiMBF,y$CC)
ycSW _MULTIPLY(yhMBF,y$CC)
ycSC _MULTIPLY(yhMBF,y$T2)

Since most people define costs and prices as time-dependent yields, the example above may or may not work. If you define stumpage prices as time-dependent, but they are static (no price appreciation), the syntax above works fine. But if you have price appreciation, Woodstock will throw the following error:

Error: 386, line number(2539), in file C:\Work\Model.yld 
       yrPLP _MULTIPLY(yplp,yplp$)
Incorrect use of internal functions. You cannot mix Age-dependent and Time-dependent yield curves. 

There is a work-around if you're willing to use older code:

*YC .MASK()
yrPWD yPWD * y$PWD

If you have a lot of yield tables to process, the interpreted code is much slower than the functions like _MULTIPLY( ) that are implemented using dynamic link libraries (DLLs). But that may be better than rethinking your entire model.

Sequence-Dependent Yields?

I know we are becoming an endangered species, but those of us old enough to have used FORPLAN may remember that yields could be one of age-, time- or sequence-dependent. By sequence-dependent, we mean that a yield is triggered after some other event. For example, suppose that I want to model siltation and have it triggered for a few periods after a harvest operation. The siltation does not depend on the age of the trees harvested, and while the amount of silt produced may change with time, it is not associated with any particular planning period either. It is truly sequence-dependent.

Woodstock doesn’t explicitly offer sequence-dependent yields and you may think you are out of luck modeling something like siltation. However, you can incorporate yield tables within prescriptions in the REGIMES section. For example, in the research forest model, we have a group selection prescription that consists of periodic harvests. We also have a yield table that shows changes in the presence of pollinator insects following harvest that we link directly in the prescription table.

*PRESCRIPTION rxUG01 Start removals in period 1
*OPERABLE .MASK(_TH1(F),_TH4(UGRP),_TH17(U01)) _AGE >= 1
  _RXPERIOD _ACTION _ENTRY  yxPOL
      0       aGS  _INITIAL   4.0    
      1        -       -      3.5     
      2        -       -      2.5 
      3        -       -      1.0
      4       aGS      -      4.0  
      5        -       -      3.5
     ...

I have defined the prescription rxUG01. The action aGS (group selection harvest), boosts pollinator levels to 4.0 (on a scale of 0 to 5). One period (5 years) later, pollinator levels drop to 3.5. As the canopy closes on the group selection harvest area, pollinators drop more quickly. However, following another harvest, pollinator levels are boosted once again. While you could probably construct age- or time-dependent yields to represent these, they would quickly become unwieldy if the return period between harvests is not constant (say 15 years, then 10 years and then 20 years).

If the yield table containing yxPOL is present in the YIELDS section within a time-dependent yield table, it will also be treated as time-dependent in REGIMES. That means it will be subject to the same rules for other time-dependent yields, including the restriction on mixing types using yield functions. I don’t know how yield tables in the REGIMES section are represented in the Woodstock code (as age- or time-dependent) if the yield component is not present in the YIELDS section.

Confused by Regimes and Sequence-Dependent Yields? Contact Me!

If you are interested in modeling innovative silviculture or other uncommon formulations, give me a shout! I have lots of experience modeling different types of silviculture from around the world and I'm sure I can help you devise an efficient formulation.

Friday, May 24, 2024

Getting Good Graphs From Woodstock

Introduction

I’m always surprised when I learn that clients do not use the Woodstock graphics section to produce on-screen graphs of their solutions. Frankly, I can’t conceive of doing an analysis without monitoring standing inventory, harvest levels, operable inventory and silvicultural activity areas. If nothing else, a graph will quickly indicate if you forgot to generate a new schedule after imposing an even-flow constraint or something similar. Seeing how inventory is changing can provide insights into why harvest levels don't seem to be what you expect.

That being said, the default color choices that Remsoft provides are at best, meh. People who know me know that I despise the “mustard” color, which to me looks more like baby poop. They also know that I don’t think much of 3D charts (which are hard to interpret), line charts with all sorts of markers, and graphs with too many outputs graphed simultaneously. There are good resources on how to display data, but Woodstock doesn’t enforce any of these rules. It is up to you to make something informative and visually pleasing.

About the Graphics Colors

Unfortunately, you cannot redefine Woodstock’s standard color codes to more pleasing shades. These are, RED, GREEN, BLUE, YELLOW, CYAN, MAGENTA, SCARLET, EMERALD, NAVY, MUSTARD, PEACH, ORANGE, BROWN, PURPLE, GREY, and BLACK. However, you can tell Woodstock that you want to use a custom color instead of one of the standard colors.

Right-click the graph and bring up the Properties dialog. Select the output you want to change and click the More (…) button. You can choose one of the standard Windows colors, or you can define your own by clicking in the rainbow box, or by specifying HSL percentages (hue, saturation, luminence) or RGB decimal values.

How to customize Woodstock graph colors

Once you’ve selected and saved your custom color, Woodstock will save your choice in the Graphics section using the _RGB() function, which uses decimal numbers instead of hex.

 *LINES
 OICE 1 1000 _RGB(203,24,29) _SOLID "Cedar "
 OIPW 1 1000 _RGB(107,174,214) _SOLID "Ponderosa & white pines"
 OIDF 1 1000 _RGB(35,139,69) _SOLID "Douglas-fir, larch, spruce"
 OIWW 1 1000 _RGB(204,204,204) _SOLID "Whitewoods "
 OILP 1 1000 _RGB(33,113,181) _SOLID "Lodgepole pine"

That is all well and good, but if you save a bunch of custom colors, how do you use them in your next modeling project? You could copy and paste from your graphics section to inherit the colors, but the better approach is to edit the Woodstock palette.ini file and save them.

The WKPalette.ini File

In your Remsoft installation folder (e.g., C:\Remsoft\Ver2023.02.01), you will find a text file called wkpalette.ini. It is just a text file with a bunch of colors defined by hexadecimal RGB values. For example, #000000 is the RGB code for black, where red = 00, green = 00 and blue = 00. White would be #FFFFFF, with all three colors set to the maximum 255 (FF in hex). All other colors are some combination of RGB values ranging from black (00) to white (FF). Greys are easy because they are all the same value for red, green and blue (i.e., #969696 is a mid grey).

If you have decided on colors previously, you can save them as a new palette in the wkpalette.ini. First, save the old file as wkpalette.old, just in case you muck things up. Then open the file in any text editor (Notepad, Woodstock)

[Palette:_Default]
#000000
#808080
#FF8000
#804000
#0000FF
#000080
#FFFF00
#C2C200
#8000FF
#800080
#C20000
#008000
#21A161
#FF80C2
#FFC080
3FF0000

There are multiple palettes defined in the file, which can be selected from the Options menu of the Graphics window. 


How to choose alternative palettes

The best source I know of for choosing pleasing but contrasting color combination in your maps is the ColorBrewer 2.0 website. Designed for GIS professionals, it has numerous options for displaying different kinds of data on choropleth maps. But the same concepts also apply to graphs. 

For a project I am currently working on, one of the team members is red-green color blind. After researching some websites on the subject, I selected a color palette that should allow anyone to discern differences in the data.

[Palette: ColorBlind]
#000000
#E69F00
#648FFF
#009E73
#F0E442
#0072B2
#D55E00
#CC79A7
#332288
#117733
#44AA99
#88CCEE
#DDCC77
#CC6677
#AA4499
#882255

Results

The colors in the new palette (right) are softer than the Remsoft defaults (left):



Alternative palettes: Remsoft Default (L) vs Custom Color Blind (R)

Summary

In addition to colors, you can also choose different fonts. The standard Tahoma fonts look good, but for those who like a fixed pitch font, Consolas is also nice. I use it as the text font in the Woodstock editor. I believe it displays better on screen than the default Courier New. When I am teaching new analysts, it is important to be able to distinguish spaces and underscores, and that's hard to do with Courier font. 

NOTE: If you change the palette file, you'll also have to change the palette setting in your Woodstock graphs if you want the graph colors to change. And remember, colors are just randomly chosen when you first define graphs. You still have to manually change them if you find the same colors repeated, etc. Have fun!

Interested in How to Better Present Results? Contact Me!

Model diagnostics are very important which is why I think using graphs is mandatory. With graphs, you can immediately see if constraints are being respected and if your outputs are calculating properly. I can lead a custom session for your staff on how to do QA and model testing right!

Wednesday, May 15, 2024

Errors of Omission and Commission

Introduction

Recently, I've been working on a modeling project with a lot of interesting silviculture. In addition to the usual suspects (clearcutting, commercial thinning), this model includes group selection, individual tree selection and variable retention harvesting. Devising the conceptual model for this project was challenging (something I'll talk about in another post), but keeping track of the many different yield tables was equally so. Specifically, it was a challenge to know that I was associating the correct yield table with each stand, AND that I wasn't inadvertently missing tables. So, in this post, I’m going to review errors of omission and commission and highlight a feature in the Woodstock editor that can be a big help.

Errors of Omission

You are committing an error of omission in your model when you reference a cost coefficient that doesn’t exist. For example, if you have stumpage prices by district, but you forgot to include one district in the YIELDS section, Woodstock assumes a value of 0 for prices in the missing district. Normally, that will just generate the familiar Warning 74 (yield not found, assume 0), despite the fact that it is a major problem.

Luckily, you can avoid errors of omission fairly easily by following a simple methodology based on this idea: never rely on Woodstock to assume 0 for you:

Define all yield sets with the same yield components (columns). If a product doesn’t exist in a stand, then print a column of zeros. Yes, Woodstock will complain (Warning 328) that you don’t need that column. You can mute that error code in the CONTROL section.

If you include non-productive areas in your model, assign them an explicit yield set as well. You just need a single row of zeros under each yield component heading for AGE or CP = 1.

If you do this, you should be able to check for missing yields using any of your defined yield components (e.g., ytmvt) using the following report:

*TARGET yield_check.dbf
ytmvt

Open the report file, and sort the YldNumber column in ascending order. If there are blanks in the YldNumber column, the development types shown do not have yield tables. Woodstock has assumed 0 for you. 



Excerpt of Yield_Check report

How does it work?

Internally, Woodstock maintains a list of YldNumbers (corresponding to each yield component) to which each development type points. If a development type does not point to a yield component, it triggers the warning 74.

By ensuring that every development type points to an instance of every yield component, there should be no gaps in the yield report. Yes, it does require redundant information that must be stored in memory, but the trade-off is knowing that you are not missing any real yield information.

The DevTypes you should be most concerned about are existing types that have never been treated. These ALL should have matching yield tables or else your initial inventory numbers will be off.

You may also have missing yields for specific timing choices of an action, even though all existing untreated stands are accounted for. This can arise when you have specified a general operability statement, but some conditions do not have yields associated with them. You can apply very specific DevType masks on multiple lines to account for every single stand, but this may not be worth the effort to account for them all. If the number of missing yield tables is small and only represents a small subset of some specific timing choices, all may be fine. When Woodstock evaluates those timing choices, the 0 coefficients associated with them should cause those timing choices to never be chosen.

Errors of Commission

You are committing an error of commission when you point to wrong information in your model. For example, this can occur if you associate the wrong price coefficient in your output definition, or if Woodstock matches an incorrect yield table with a development type. Unfortunately, errors of commission are much harder to find than errors of omission. There is no easy "Wrong_Yield" report you can use to identify them.

Avoiding these types of errors requires diligence and a methodical approach to model development. You should use the *GROUP keyword in your OUTPUTS section to develop all your outputs. Just like an outline in a Word document, you can use *GROUP to identify all the potential outputs you will need. Then, within each *GROUP, systematically work through all the variations on outputs that required. Copy and paste operations often result in overlooked edits that lead to errors of commission. Automation tools like Integrator or Python are better at ensuring that you are enumerating all the possible combinations with the right yield component.

CTRL-M - a handy tool

My favorite newer feature of the Woodstock editor is the CTRL-M (mask search) function. This feature works very much like the CTRL-F (Find) function but it isn’t literal like CTRL-F. For example, if you search a file for the string ‘CH00018 ? ? ? ? UT’, Woodstock will look only for strings where there is a single space between question marks. It will not find this string where there are two spaces between the question mark and UT : ‘CH00018 ? ? ? ? UT’. Unless you are very consistent in spacing, CTRL-F can be of limited utility if you’re trying to find a matching yield table for a particular development type.

If you open the CRTL-M dialog, you can enter a fully defined development type mask, with or without global and aggregate attributes. When you click Find, the Woodstock editor searches for masks that match, including global attributes. For example, suppose you input the following string into the CTRL-M dialog:

'FEE NONE NONE FC Y CSP CH00018 FE N WH I UT 250 ZR6024 1 LBC'. 

In your YIELDS section, CTRL-M will locate this yield set header:

*Y ? ? ? ? ? ? CH00018 ? ? ? ? UT ? ? ? ? 

and this one,

*Y ? ? ?      ? ? ?   CH00018 ? ?         ? ? UT ? ? ? ?

and also this one,

*Y ? ? ? ? ? ? agGRP8 ? ? ? ? ? ? ? ? ?

In the first instance, the mask search matches to global attributes (?) in all themes except TH7 and TH12. The second instance is exactly the same, but spacing does not matter, as it would in the Find function. With the third instance, the mask search also locates yield headers with valid aggregate attributes - the aggregate agGRP8 includes CH00018.

A Potential Use Case for CTRL-M

Let's assume that you’ve noticed you are getting incorrect volumes reported, and you’ve identified one of the development types that is yielding wrong values. Using CTRL-M, you start searching your YIELDS section for matching yields. You expect to find the correct yield in the bottom third of the file, but instead you find a match almost immediately. When you search again, you find the yield set you were expecting. You have a problem! Remember, Woodstock only searches for yield information until it finds a first match; later matches are ignored.

Now that you’ve found the problem yield table, the question is, why is it there? Did your yield generation process include old inventory information that produced the errant yield table? Was this just temporary code used earlier in the development process? Regardless of how it got there, it can be easily removed. However, if you have an automated process or you rely on contractors to provide the data, you need to make sure that future yield updates do not create the problem once again.

Is Your Model Driving You Crazy? Contact Me!

Finding errors of omission and commission can be difficult in a large complex model. If you’d like a new set of eyes to look over your model(s), give me a shout to schedule a model audit Afterwards, we can schedule an on-site training review with your staff to review recommendations for avoiding problems in the future.

If you have a topic that you'd like me to address in a future post, send me a message. I'm always more interested in helping with client issues than ones that I think might be of interest.

Monday, April 29, 2024

Regimes - Why Aren't People Using Them?

Introduction

If memory serves me correctly, Remsoft introduced the Regimes module back in 2009. At first, it did not generate a lot of interest, despite criticisms from some circles that Model I was better than Model II. It was an extra module to license, and frankly, it wasn't the easiest syntax to wrap your head around. But times have changed!

Regimes is now part of the base Woodstock license. And while the syntax isn't the easiest thing to master, I've come to appreciate how Ugo was able to incorporate Model I structure into Woodstock's inherent Model II framework. As a user of FORPLAN, I remember how meshing Model I and Model II in a single model resulted in a very clumsy interface. It was no surprise that few people used FORPLAN v.2. But it has been 15 years since Woodstock Regimes was released, and yet I still don't see a lot of clients using it. This poll I conducted confirms that:

Poll conducted week of April 22, 2024

Why Use Regimes?

In Woodstock, when you specify an action, you provide the operability criteria (development type + condition) that allows the interpreter to generate a decision variable for each DevType-condition combination. If there are 5 operable DevTypes and 5 conditions (e.g. age) when each DevType can be harvested, the interpreter will generate 25 decision variables. The beauty of Regimes is that it allows you to create decision variables for a sequence of events, rather just a single activity. 

So, for what kinds of forest management decisions are Regimes suited? I've seen many models in the US South where commercial thinning is always followed by a fertilization one year later. While the timing of the thinning is a decision, the fertilization is not. Before Regimes, modeling CT + fert required inventory outputs with somewhat unintuitive DevType masks to make it work and report the fertilization acres in the correct period:

*SOURCE .MASK(_THx(14)) @AGE(16) _INVENT _AREA
; add 1 to age because of age counter

Additionally, by relying on an inventory output, it makes your model run slower.

*REGIME rThin
*OPERABLE rThin
   .MASK(_THx(00)) _AGE >= 13 AND _AGE <= 18

 *PRESCRIPTION rxTF (thin and fert)
   _RXPERIOD _ACTION   _ENTRY 
       0       aTH    _INITIAL
       1       aFT     _FINAL

Using a regime and prescription, reporting acres treated relies only on action-based outputs and so your model runs faster.

What About Reforestation?

The poll numbers are interesting for a few reasons.

Two-thirds of the respondents have specific planting actions in their models. From a conceptual modeling perspective, this implies that there are alternatives for each harvested area (timing, species, planting density, etc.).

No one simply attributes reforestation acres and costs to harvest actions. I know there are many Woodstock users in Canada, and few of them use 1-year planning periods. Again, this implies reforestation alternatives for each harvested area.

Roughly one-third of the respondents are using regimes to model reforestation. (Disclosure: respondents work for client organizations for which I developed models recently).

Over the last 20 years, I've witnessed a shift from silvicultural evaluation models to highly prescriptive models. By this I mean that 20 years ago, a lot of the models I built evaluated multiple planting densities for each harvested area. As a result, there needed to be multiple planting actions to represent the alternatives. And because planting was a choice, there needed to be constraints to enforce reforestation policies.

These days, however, while there may be different reforestation prescriptions across the forest, there is generally only one prescription for each harvest area. And in that case, there really isn't a need to have separate decision variables for planting. Instead, one or prescriptions in a reforestation regime that begins with final harvest is all that is required. Fewer decision variables = faster matrix generation and solution. And an added bonus is that the reforestation constraints may not be required either.

So Why Haven't Things Changed?

While I was at Remsoft, we always taught reforestation as a distinct activity from harvesting in the Modeling Fundamentals course. I suspect that many of the analysts we taught just replicated that conceptual modeling framework in the models they developed. And my experience has been that organizations rarely revamp models once developed.

Because models are rarely scrapped and rebuilt, I suspect most analysts these days inherited models built by some predecessor (who probably has now left the organization). There is never time to redesign a model so the same structure persists, with annual updates to the AREAS file (to account for depletions) and yield tables.

Ready for a Change? Contact Me!

If your models haven't been updated in a while, maybe it is time to rethink things. Together, we can design a new conceptual framework that is efficient and easier to debug if problems arise. And if you'd like to apply some targeted training (for say, Regimes or conceptual modeling) we can do that too!

What is the Big-M Method, and Why Should I Care?

The  Big-M method  is a common modeling technique used in  Mixed Integer Programming (MIP)  to represent  logical conditions  or  implicatio...