Skip to content
Snippets Groups Projects
Select Git revision
  • 69c75fcb850ce2dd13f05c66f1068729262830c1
  • main default protected
  • test_coef
  • 21-things-to-take-care-of-before-submission
4 results

mxl_wtp_space_T_NR_caseB.R

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    new-almass.jl 14.73 KiB
    # Functions translated from ALMaSS for use in Persefone
    #
    # ALMaSS is licensed as follows:
    #
    # Copyright (c) 2011, Christopher John Topping, Aarhus University
    # All rights reserved.
    #
    # Redistribution and use in source and binary forms, with or without modification, are permitted provided
    # that the following conditions are met:
    #
    # Redistributions of source code must retain the above copyright notice, this list of conditions and the
    # following disclaimer.
    # Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
    # the following disclaimer in the documentation and/or other materials provided with the distribution.
    #
    # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
    # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
    # FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
    # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
    # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
    # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    
    
    # TODO
    # - almass_RecalculateBugsNStuff()
    # - tov_MaizeStrigling, etc
    # - radconv = model.SolarConversion[is_c4_plant + 1][index + 1]
    
    
    # `class VegElement : public LE` from `Landscape/Elements.h`, line 601
    @kwdef mutable struct VegElement
        # Note: this class has a lot of fields, only the fields actually
        #       used are here
    
        # new_weed_growth::Float64
        force_growth::Bool = false
        yddegs::Float64 = 0
        ddegs::Float64 = 0
        vegddegs::Float64 = 0
        curve_num::Int = 1
        veg_phase::Int = 1 # TODO: enum type?
        growth_scaler::Float64 = 1
        LAgreen::Float64 = 0
        LAtotal::Float64 = 0
        veg_height::Float64 = 0
        force_LAgreen::Float64 = 0
        force_LAtotal::Float64 = 0
        force_veg_height::Float64 = 0
    
        newgrowth::Float64 = 0
        veg_cover::Float64 = 0
    
        vege_type::Int = 1
        oldLAtotal::Float64 = 0
        biomass_scale::Vector{Float64} = [1.0]
        owner_index::Int = 1
        poly::Int = 1
        veg_biomass::Float64 = 0
        area::Float64 = 1
        total_biomass::Float64 = 0
        total_biomass_old::Float64 = 0
        veg_density::Float64 = 0
        green_biomass::Float64 = 0
        dead_biomass::Float64 = 0
    end
    
    function Base.show(io::IO, ve::VegElement)
        println(io, "VegElement:")
        for field in fieldnames(typeof(ve))
            println(io, "    $field: ", getfield(ve, field))
        end
    end
    
    # `typedef enum { ... } Growth_Phases` from `Landscape/Plants.h`, line 51
    @enum Growth_Phases begin
        janfirst = 1
        sow
        marchfirst
        harvest1
        harvest2
    end
    
    # `class CropGrowth` from `Landscape/Plants.h`, line 67
    @kwdef struct CropGrowth
        # Note:
        # - 5 growth phases (enum Growth_Phases)
        # - 3 curves (leaf_area_green = 0, leaf_area_total, height)
        lownut::Bool = false
        dds::Vector{Vector{Float64}}  # ddegs at inflection, double m_dds[5][MaxNoInflections] = { 0 }
        slopes::Vector{Vector{Vector{Float64}}}  # double m_slopes[5][3][MaxNoInflections] = { 0 }
        start::Vector{Vector{Float64}}  # double m_start[5][3] = { 0 }
        start_valid::Vector{Bool}  # bool m_start_valid[5] = { 0 }
    end
    
    # `class PlantGrowthData` from `Landscape/Plants.h`, line 79
    struct PlantGrowthData
        growth::Vector{CropGrowth}
        #final_ddeg::Vector{Vector{Vector{Float64}}}
        numbers::Vector{Int}
        # num_crops::Int
        # Note: some more fields ommitted related to weeds and bugs
    end
    
    # `void VegElement::DoDevelopment(void)` from `Landscape/Elements.cpp`, line 2254
    function almass_DoDevelopment(ve::VegElement, model::SimulationModel)
        # ve.new_weed_growth = 0.0
        if ! ve.force_growth
            # first do the day degree calculations
            ve.yddegs = ve.ddegs
            pos_temp_today = almass_GetDDDegs(model)
            if ve.vegddegs != -1.0
                # sum up the vegetation day degrees since sowing
                ve.vegddegs += pos_temp_today
            end
            # sum up the phase day degrees
            ve.ddegs = pos_temp_today + ve.yddegs
    
            pgd = get_plant_growth_data(model)
            dLAG = almass_GetLAgreenDiffScaled(pgd, ve.ddegs, ve.yddegs, ve.curve_num, ve.veg_phase, ve.growth_scaler)
            dLAT = almass_GetLAtotalDiffScaled(pgd, ve.ddegs, ve.yddegs, ve.curve_num, ve.veg_phase, ve.growth_scaler)
            dHgt = almass_GetHeightDiffScaled(pgd, ve.ddegs, ve.yddegs, ve.curve_num, ve.veg_phase, ve.growth_scaler)
    
            ve.LAgreen += dLAG
            if ve.LAgreen < 0.0
                ve.LAgreen = 0.0
            end
            ve.LAtotal += dLAT
            if ve.LAtotal < 0.0
                ve.LAtotal = 0.0
            end
            ve.veg_height += dHgt
            if ve.veg_height < 0.0
                ve.veg_height = 0.0
            end
    
            # TODO: ignored code for weeds
            #       if (this->m_owner_index != -1) ...
            #           // This only works because only crops and similar structures have owners
        else
            almass_ForceGrowthDevelopment(ve)
        end
    
        # check that ve.LAtotal is bigger than ve.LAgreen
        if ve.LAtotal < ve.LAgreen
            @warn "ve.LAtotal < ve.LAgreen, $(ve.LAtotal) < $(ve.LAgreen). Performing hack correction"
            ve.LAtotal = 1.1 * ve.LAgreen
        end
    
        # Note: this calculates the biomass etc, not just "BugsNStuff"
        almass_RecalculateBugsNStuff(ve, model)
    
        # Note: missing code
        # - # Here we need to set today's goose numbers to zero in case they are not written
        #   # by the goose population manager (the normal situation)
        #   ResetGeese()
        # - # Deal with any possible unsprayed margin, transferring info as necessary
        #   if (GetUnsprayedMarginPolyRef() != -1) ...
        # - m_interested_green_biomass = m_green_biomass * m_interested_biomass_fraction
    end
    
    # `double Weather::GetDDDegs(long a_date)` from `Landscape/Weather.cpp`, line 205
    function almass_GetDDDegs(model::SimulationModel)
        basetemp = 0.0  # TODO: doesn't seem to use a min growth temp ???
        #return bounds((maxtemp(model) + mintemp(model)) / 2 - basetemp)
        return max((maxtemp(model) + mintemp(model)) / 2 - basetemp, 0.0)
    end
    
    # `double PlantGrowthData::GetLAgreenDiffScaled(...)` from `Landscape/Plants.h`, line 136
    function almass_GetLAgreenDiffScaled(pgd::PlantGrowthData, ddegs::Float64, yddegs::Float64,
                                         curve_num::Int, veg_phase::Int, growth_scaler::Float64)
        return growth_scaler * almass_GetLAgreenDiff(pgd, ddegs, yddegs, curve_num, veg_phase)
    end
    
    # `double PlantGrowthData::GetLAtotalDiffScaled(...)` from `Landscape/Plants.h`, line 138
    function almass_GetLAtotalDiffScaled(pgd::PlantGrowthData, ddegs::Float64, yddegs::Float64,
                                         curve_num::Int, veg_phase::Int, growth_scaler::Float64)
        return growth_scaler * almass_GetLAtotalDiff(pgd, ddegs, yddegs, curve_num, veg_phase)
    end
    
    # `double PlantGrowthData::GetHeightDiffScaled(...)` from `Landscape/Plants.h`, line 140
    function almass_GetHeightDiffScaled(pgd::PlantGrowthData, ddegs::Float64, yddegs::Float64,
                                        curve_num::Int, veg_phase::Int, growth_scaler::Float64)
        return growth_scaler * almass_GetHeightDiff(pgd, ddegs, yddegs, curve_num, veg_phase)
    end
    
    # `double PlantGrowthData::GetLAgreenDiff(...)` from `Landscape/Plants.h`, line 106
    function almass_GetLAgreenDiff(pgd::PlantGrowthData, ddegs::Float64, yddegs::Float64,
                                   curve_num::Int, veg_phase::Int)
        # TODO: hardcoded 1, in original code it is 0
        return almass_FindDiff(pgd, ddegs, yddegs, curve_num, veg_phase, 1)
    end
    
    # `double PlantGrowthData::GetLAtotalDiff(...)` from `Landscape/Plants.h`, line 116
    function almass_GetLAtotalDiff(pgd::PlantGrowthData, ddegs::Float64, yddegs::Float64,
                                   curve_num::Int, veg_phase::Int)
        # TODO: hardcoded 2, in original code it is 1
        return almass_FindDiff(pgd, ddegs, yddegs, curve_num, veg_phase, 2)
    end
    
    # `double PlantGrowthData::GetHeightDiff(...)` from `Landscape/Plants.h`, line 125
    function almass_GetHeightDiff(pgd::PlantGrowthData, ddegs::Float64, yddegs::Float64,
                                  curve_num::Int, veg_phase::Int)
        # TODO: hardcoded 3, in original code it is 2
        return almass_FindDiff(pgd, ddegs, yddegs, curve_num, veg_phase, 3)
    end
    
    # `double PlantGrowthData::FindDiff(...)` from `Landscape/Plants.cpp`, line 45
    function almass_FindDiff(pgd::PlantGrowthData, ddegs::Float64, yddegs::Float64,
                             curve_num::Int, veg_phase::Int, type::Int)
        # Note: curve_num is a_plant in original code
        #
        # Note: curve_num is an index like in the CropGrowth.txt file,
        #       *not* indices into the m_growth array
    
        # TODO: Comments from original code
        #
        # // Check for valid plant number at runtime?
        # // This is broken for growth curves where one can risk passing
        # // more than a single inflection point in the growth curve in a
        # // single day...
    
        index = pgd.numbers[curve_num]  # index = m_numbers[ a_plant ]
        oldindex = 0
        newindex = 0
    
        # TODO: magic constant 99999
        if first(pgd.growth[index].dds[veg_phase]) == 99999
            return 0.0
        end
    
        dds_for_phase = pgd.growth[index].dds[veg_phase]
    
        # TODO: breaks on last index
        for i in eachindex(dds_for_phase)
            # // In other words: If the current value for summed day degrees
            # // is smaller than the X position of the *next* inflection
            # // point, then we are in the correct interval.
            if i == lastindex(dds_for_phase) || dds_for_phase[i + 1] > ddegs
                newindex = i
                break
            end
        end
    
        # TODO: illegal access for last index
        for i in eachindex(dds_for_phase)
            if i == lastindex(dds_for_phase) || dds_for_phase[i + 1] > yddegs
                oldindex = i
                break
            end
        end
    
        if newindex > oldindex
            # // We have passed an inflection point between today and yesterday.
            # // First add the increment from yesterdays day degree sum up to
            # // the inflection point.
            dddif = dds_for_phase[newindex] - yddegs
            diff = pgd.growth[index].slopes[veg_phase][type][oldindex] * dddif
    
            # // Then from the inflection point up to today.
            dddif = ddegs - dds_for_phase[newindex]
            diff += pgd.growth[index].slopes[veg_phase][type][newindex] * dddif
        else
            # // No inflection point passed.
            diff = pgd.growth[index].slopes[veg_phase][type][newindex] * (ddegs - yddegs)
        end
    
        return diff
    end
    
    # `double VegElement::ForceGrowthDevelopment(...)` from `Landscape/Elements.cpp`, line 2223
    function almass_ForceGrowthDevelopment(ve::VegElement)
        # if ( m_herbicidedelay <= 0 ) {
        #     m_weed_biomass += m_force_Weed; // ***CJT*** 12th Sept 2008 - rather than force growth, weeds might be allowed to grow on their own
        #   if(m_force_Weed>0) m_new_weed_growth = m_force_Weed;
        # }
    
        ve.LAgreen += ve.force_LAgreen
        ve.LAtotal += ve.force_LAtotal
        ve.veg_height += ve.force_veg_height
    
        if ve.LAgreen < 0
            ve.LAgreen = 0
        end
        if ve.LAtotal < 0
            ve.LAtotal = 0
        end
        if ve.veg_height < 0
            ve.veg_height = 0
        end
    end
    
    # `void VegElement::RecalculateBugsNStuff(void)` from `Landscape/Elements.cpp`, line 1704
    function almass_RecalculateBugsNStuff(ve::VegElement, model::SimulationModel)
        # Calculate vegetation cover and vegetation biomass
        #
        # Original comment:
        #
        # /** This is the heart of the dynamics of vegetation elements. It
        # calculates vegetation cover and uses this to determine
        # vegetation biomass.  It also calculates spilled grain and goose
        # forage, as well a calculating insect biomass, vegetation density
        # and dead biomass*/
    
        ve.newgrowth = 0
        # `cfg_beer_law_extinction_coef` from `Landscape/Elements.cpp`, line 139
        beer_law_extinction_coef = 0.6
        # Beer's Law to give cover
        ve.veg_cover = 1.0 - exp(- beer_law_extinction_coef * ve.LAtotal)
        # This is used to calc growth rate
        usefull_veg_cover = 1.0 - exp(-0.4 * ve.LAgreen)
    
        # global radiation today
        glrad = almass_SupplyGlobalRadiation(model)
    
        # This is different for maize (a C4 plant)
        is_c4_plant = ((ve.vege_type == tov_Maize) ||
            (ve.vege_type == tov_OMaizeSilage) ||
            (ve.vege_type == tov_MaizeSilage) ||
            (ve.vege_type == tov_MaizeStrigling))
        # TODO: simplify calculation of `index`
        index = Int(floor(0.5 + almass_SupplyTemp(model)) + 30 + 1) # // There are 30 negative temps
        # TODO: model needs this field???
        # Note: +1 because Julia arrays are 1-based
        radconv = almass_SolarConversion(model, is_c4_plant, index)
    
        if ve.LAtotal >= ve.oldLAtotal
            # we are in positive growth so grow depending on our equation
            ve.newgrowth = usefull_veg_cover * glrad * radconv * ve.biomass_scale[ve.vege_type];
            if ve.owner_index != -1
                # This only works because only crops and similar structures have owners
                fintensity = almass_SupplyFarmIntensity(model, ve.poly)
                if fintensity >= 1
                    # 1 means extensive, so reduce vegetation biomass by 20%
                    # NB this cannot be used with extensive crop types otherwise you get an additional 20% reduction
                    # This way of doing things provides a quick and dirty general effect.
                    ve.veg_biomass += ve.newgrowth * 0.8
                else
                    ve.veg_biomass += ve.newgrowth
                end
            else
                ve.veg_biomass += ve.newgrowth
            end
        else
            # Negative growth - so shrink proportional to the loss in LAI Total
            if ve.oldLAtotal > 0
                temp_propotion = ve.LAtotal / ve.oldLAtotal
                # //m_newgrowth = m_veg_biomass*(temp_propotion-1);
                ve.veg_biomass *= temp_propotion;
            end
        end
    
        # Here we also want to know how much biomass we have on the field
        # in total. So we multiply the current biomass by area
        ve.total_biomass = ve.veg_biomass * ve.area
        ve.total_biomass_old = ve.total_biomass
        # NB The m_weed_biomass is calculated directly from the curve in Curves.pre
        # rather than going through the rigmarole of converting leaf-area index
        ve.veg_density = Int(floor(0.5 + (ve.veg_biomass / (1 + ve.veg_height))))
        if ve.veg_density > 100
            # to stop array bounds problems
            ve.veg_density = 100
        end
    
        if ve.LAtotal == 0.0
            ve.green_biomass = 0.0
        else
            ve.green_biomass = ve.veg_biomass * (ve.LAgreen / ve.LAtotal)
        end
        ve.dead_biomass = ve.veg_biomass - ve.green_biomass
        #// Here we use our member function pointer to call the correct piece of code for our current species
        #(this->*(SpeciesSpecificCalculations))();
    end