diff --git a/GAMS/IntExtOpt.gms b/GAMS/IntExtOpt.gms index 2d7b2a042a30f70cb25d0034408ed0d3484576af..1fb6a0c0b80947bdbb83d913404ffbbde98d235a 100644 --- a/GAMS/IntExtOpt.gms +++ b/GAMS/IntExtOpt.gms @@ -137,6 +137,8 @@ $gdxin maxNetImport(import_crop) = min(maxNetImport(import_crop), demand(import_crop)); minNetImport(import_crop) = min(minNetImport(import_crop), demand(import_crop)); + + woodDemand = 0; VARIABLES cropArea(crop, location) total area for crops - Mha @@ -280,11 +282,11 @@ $gdxin NEW_FOREST_POTENTIAL_HARVEST(location) .. newForestPotentialHarvest(location) =E= sum(land_cover, landCoverChange(land_cover, 'timberForest', location) * woodYieldRota(land_cover, 'timberForest', location)); - WOOD_HARVEST_NATURAL_CALC(location) .. woodHarvestNat(location) =E= naturalDeforestedArea(location) * woodYieldLuc('natural', 'pasture', location); + WOOD_HARVEST_NATURAL_CALC(location) .. woodHarvestNat(location) =E= naturalDeforestedArea(location) * woodYieldLuc('natural', 'natural', location); WOOD_HARVEST_NATURAL_CONSTRAINT(location) .. naturalDeforestedArea(location) =L= landCoverChange('natural', 'natural', location); - WOOD_HARVEST_LUC_CALC(location) .. woodHarvestLuc(location) =E= sum((forested, land_cover_after), landCoverChange(forested, land_cover_after, location) * woodYieldLuc(forested, land_cover_after, location)); + WOOD_HARVEST_LUC_CALC(location) .. woodHarvestLuc(location) =E= sum((forested, exc_natural), landCoverChange(forested, exc_natural, location) * woodYieldLuc(forested, exc_natural, location)); WOOD_SUPPLY_CALC .. woodSupply =E= woodFromRotation + sum(location, woodHarvestNat(location) + woodHarvestLuc(location)); diff --git a/debug_config.properties b/debug_config.properties index ff1a0558f74100e5e3652705bab90a79499f691b..bdbbde9e7835bb0863893a953eb35015e6f65a36 100644 --- a/debug_config.properties +++ b/debug_config.properties @@ -18,19 +18,19 @@ GENERATE_NEW_YIELD_CLUSTERS=false YIELD_FILENAME=yield.out -DEBUG_LIMIT_COUNTRIES=true -DEBUG_COUNTRY_NAME=Central America -GAMS_COUNTRY_TO_SAVE=Central America +DEBUG_LIMIT_COUNTRIES=false +DEBUG_COUNTRY_NAME=United States of America +GAMS_COUNTRY_TO_SAVE=United States of America -INIT_WOOD_PRICE=0.4 +INIT_WOOD_PRICE=0.0 INIT_WOOD_STOCK=1000 INIT_CARBON_PRICE=0.0 FOREST_ROTATION_PERIOD=30 INFRASTRUCTURE_EXPANSION_COST=0.0 MANAGED_FOREST_INCREASE_COST=0.2 MANAGED_FOREST_DECREASE_COST=0.2 -FOREST_MANAGEMENT_COST=0.2 +FOREST_MANAGEMENT_COST=0.25 FOREST_MAX_CHANGE=0.02 -LAND_CHANGE_COST=0.2 -NATURAL_AREA_VALUE=0.01 -VEGETATION_CLEARING_COST=0.25 \ No newline at end of file +LAND_CHANGE_COST=0.5 +NATURAL_AREA_VALUE=0.0 +VEGETATION_CLEARING_COST=0.05 \ No newline at end of file diff --git a/src/ac/ed/lurg/InternationalMarket.java b/src/ac/ed/lurg/InternationalMarket.java index 06deaee9eedbf6058455276829a4d8989ae7db2d..cf260102bf48f0ec742de58154a198253c44fd44 100644 --- a/src/ac/ed/lurg/InternationalMarket.java +++ b/src/ac/ed/lurg/InternationalMarket.java @@ -88,7 +88,6 @@ public class InternationalMarket { double countryNetImports = entry.getValue().getShockedNetImports(); totalProduction.incrementValue(c, (entry.getValue().getProductionExpected()-entry.getValue().getProductionShock())); - //TODO this doesn't work when both imports and exports > 0 if (countryNetImports > 0) totalImportCommodities.incrementValue(c, countryNetImports); else @@ -138,6 +137,7 @@ public class InternationalMarket { double totalWoodImport = 0; double totalWoodExport = 0; double totalWoodProduction = 0; + for (AbstractCountryAgent ca : countryAgents) { Map<WoodType, WoodUsageData> woodUsageMap = ca.getWoodUsageData(); for (WoodUsageData woodUsage : woodUsageMap.values()) { diff --git a/src/ac/ed/lurg/ModelMain.java b/src/ac/ed/lurg/ModelMain.java index f2a1adcef486baa6154aa297ab8cb5d773e04c82..f25cddc194a0403f92fb028843bfacdb06d607c4 100644 --- a/src/ac/ed/lurg/ModelMain.java +++ b/src/ac/ed/lurg/ModelMain.java @@ -7,12 +7,9 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; - import ac.ed.lurg.carbon.CarbonFluxRasterSet; import ac.ed.lurg.carbon.CarbonFluxReader; import ac.ed.lurg.country.AbstractCountryAgent; @@ -30,12 +27,12 @@ import ac.ed.lurg.demand.CalorieManager; import ac.ed.lurg.demand.DemandManagerFromFile; import ac.ed.lurg.demand.DemandManagerSSP; import ac.ed.lurg.demand.ElasticDemandManager; -import ac.ed.lurg.forestry.WoodYieldItem; import ac.ed.lurg.forestry.WoodYieldRasterSet; import ac.ed.lurg.landuse.ConversionCostReader; import ac.ed.lurg.landuse.CropUsageData; import ac.ed.lurg.landuse.CropUsageReader; import ac.ed.lurg.landuse.FPUManager; +import ac.ed.lurg.landuse.InitProtectedAreasReader; import ac.ed.lurg.landuse.IrrigationConstraintReader; import ac.ed.lurg.landuse.IrrigationItem; import ac.ed.lurg.landuse.IrrigationMaxAmountReader; @@ -51,7 +48,6 @@ import ac.ed.lurg.landuse.ProtectedAreasReader; import ac.ed.lurg.landuse.RunOffReader; import ac.ed.lurg.landuse.WoodUsageData; import ac.ed.lurg.landuse.WoodUsageReader; -import ac.ed.lurg.forestry.WoodYieldRasterSet; import ac.ed.lurg.forestry.WoodYieldReader; import ac.ed.lurg.output.LandUseOutputer; import ac.ed.lurg.output.LpjgOutputer; @@ -85,8 +81,8 @@ public class ModelMain { private IrrigationRasterSet currentIrrigationData; private RasterSet<LandUseItem> globalLandUseRaster; private RasterSet<IntegerRasterItem> clusterIdRaster; - WoodYieldReader woodYieldReader; - CarbonFluxReader carbonFluxReader; + private WoodYieldReader woodYieldReader; + private CarbonFluxReader carbonFluxReader; public static void main(String[] args) { @@ -144,7 +140,7 @@ public class ModelMain { private void doTimestep(Timestep timestep) { System.gc(); LogWriter.println("Timestep: " + timestep.toString()); - + YieldRaster yieldSurfaces = getYieldSurfaces(timestep); // this will wait for the marker file from LPJ if configured to do so getUpdateIrrigationData(timestep, yieldSurfaces); // updating currentIrrigationData @@ -162,9 +158,9 @@ public class ModelMain { double carbonDemand = demandManager.getCarbonDemand(ModelConfig.IS_CALIBRATION_RUN ? new Timestep(1) : timestep); double carbonDemandIncrease = (carbonDemand > previousCarbonDemand) ? carbonDemand - previousCarbonDemand: 0; - CarbonFluxRasterSet carbonFluxData = getCarbonFluxData(timestep); WoodYieldRasterSet woodYieldData = getWoodYieldData(timestep); - + CarbonFluxRasterSet carbonFluxData = getCarbonFluxData(timestep); + Map<LccKey, Double> conversionCosts; conversionCosts = (ModelConfig.CONVERSION_COSTS_FROM_FILE) ? new ConversionCostReader().read() : new ConversionCostReader().calcFromConfig(); @@ -180,7 +176,7 @@ public class ModelMain { internationalMarket.determineInternationalTrade(countryAgents.getAll(), gen2EcDDemand, carbonDemand, timestep); // calculate prices } internationalMarket.applyPriceShocks(timestep); - + // output results outputTimestepResults(timestep, globalLandUseRaster); } @@ -483,10 +479,12 @@ public class ModelMain { } if (ModelConfig.IS_CALIBRATION_RUN) { - //serializeLandUse(landUseRaster); - countryAgents.serializeCropUsageForAll(); - countryAgents.serializeWoodUsageForAll(); - internationalMarket.serializeGlobalPrices(); + if (timestep.isFinalTimestep()) { + serializeLandUse(landUseRaster); + countryAgents.serializeCropUsageForAll(); + countryAgents.serializeWoodUsageForAll(); + internationalMarket.serializeGlobalPrices(); + } } if (timestep.isInitialTimestep() && ModelConfig.GENERATE_NEW_YIELD_CLUSTERS) @@ -685,13 +683,18 @@ public class ModelMain { } }; - new MaxCropAreaReader(initialLC).getRasterDataFromFile(ModelConfig.HIGH_SLOPE_AREAS_FILE); + new MaxCropAreaReader(initialLC).getRasterDataFromFile(ModelConfig.HIGH_SLOPE_AREAS_FILE); // Fraction unavailable for conversion new LandCoverReader(initialLC).getRasterDataFromFile(ModelConfig.INITAL_LAND_COVER_FILE); // Land cover fractions new LandTileReader(initialLC).getRasterDataFromFile(ModelConfig.LAND_COVER_AGE_DIST_FILENAME); // Age distribution of land cover + new InitProtectedAreasReader(initialLC).getRasterDataFromFile(ModelConfig.PROTECTED_AREAS_FILE); // Protected fraction RasterSet<LandUseItem> landUseRaster = new RasterSet<LandUseItem>(initialLC.getHeaderDetails()); for (Map.Entry<RasterKey, LandCoverItem> entry : initialLC.entrySet()) { + //LogWriter.println(initialLC.getXCoordin(entry.getKey()) + " " + initialLC.getYCoordin(entry.getKey())); + if (entry.getKey().getCol()==38 && entry.getKey().getRow()==60) { + int foo = 1; + } landUseRaster.put(entry.getKey(), new LandUseItem(entry.getValue())); } @@ -717,7 +720,8 @@ public class ModelMain { /** Get carbon flux data */ private CarbonFluxRasterSet getCarbonFluxData(Timestep timestep) { - return carbonFluxReader.getCarbonFluxes(globalLandUseRaster, timestep); + //return carbonFluxReader.getCarbonFluxes(globalLandUseRaster, timestep); + return new CarbonFluxRasterSet(desiredProjection); } /** Get wood yield data */ diff --git a/src/ac/ed/lurg/Timestep.java b/src/ac/ed/lurg/Timestep.java index c92f0b95202380478cf368ba5e79f7db4a8f50fb..0798d3530310c89f698ea8827af9f6a8615d943e 100644 --- a/src/ac/ed/lurg/Timestep.java +++ b/src/ac/ed/lurg/Timestep.java @@ -47,6 +47,10 @@ public class Timestep { return timestep == ModelConfig.START_TIMESTEP; } + public boolean isFinalTimestep() { + return timestep == ModelConfig.END_TIMESTEP; + } + public Timestep getPreviousTimestep() { if (timestep == ModelConfig.START_TIMESTEP) throw new RuntimeException("Can't getPreviousTimestep for " + timestep); diff --git a/src/ac/ed/lurg/carbon/CarbonFluxItem.java b/src/ac/ed/lurg/carbon/CarbonFluxItem.java index e3ccfabdf10310021d307bdd0522e22ce5ab7e19..3514e65a58ae7394b6bb85f03fab280c28358ad5 100644 --- a/src/ac/ed/lurg/carbon/CarbonFluxItem.java +++ b/src/ac/ed/lurg/carbon/CarbonFluxItem.java @@ -3,12 +3,11 @@ package ac.ed.lurg.carbon; import ac.sac.raster.RasterItem; import java.io.Serializable; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import ac.ed.lurg.Timestep; -import ac.ed.lurg.landuse.LandUseTile; +import ac.ed.lurg.landuse.LandCoverTile; import ac.ed.lurg.landuse.LccKey; import ac.ed.lurg.types.LandCoverType; @@ -22,21 +21,16 @@ public class CarbonFluxItem implements RasterItem, Serializable { conversionCarbonFlux.put(key, cFlux); } - public void calcConversionCarbonFlux(LccKey key, Double[] cFluxes, Map<LandCoverType, ArrayList<LandUseTile>> landUseTiles, Timestep timestep) { + public void calcConversionCarbonFlux(LccKey key, Double[] cFluxes, Map<LandCoverType, LandCoverTile> landUseTiles, Timestep timestep) { + LandCoverTile tiles = landUseTiles.get(key.getFromLc()); - ArrayList<LandUseTile> tiles = landUseTiles.get(key.getFromLc()); - - if (tiles.size() == 0) { + double totalArea = tiles.getTotalConvertibleArea(); + if (totalArea == 0) { this.conversionCarbonFlux.put(key, 0.0); } else { double totalFlux = 0; - double totalArea = 0; - for (LandUseTile tile : tiles) { - if (tile.isProtected()) - continue; - int age = tile.getAge(timestep); - totalFlux += cFluxes[age] * tile.getArea(); - totalArea += tile.getArea(); + for (int age=0; age<=tiles.getMaxAgeBin(); age++) { + totalFlux += cFluxes[age] * tiles.getConvertibleArea(age); } double meanFlux = totalFlux / totalArea; this.conversionCarbonFlux.put(key, meanFlux); @@ -47,21 +41,17 @@ public class CarbonFluxItem implements RasterItem, Serializable { ecosystemCarbonFlux.put(lcType, cFlux); } - public void calcEcosystemCarbonFlux(LandCoverType lcType, Double[] cFluxes, Map<LandCoverType, ArrayList<LandUseTile>> landUseTiles, Timestep timestep) { + public void calcEcosystemCarbonFlux(LandCoverType lcType, Double[] cFluxes, Map<LandCoverType, LandCoverTile> landUseTiles, Timestep timestep) { - ArrayList<LandUseTile> tiles = landUseTiles.get(lcType); + LandCoverTile tiles = landUseTiles.get(lcType); - if (tiles.size() == 0) { + double totalArea = tiles.getTotalConvertibleArea(); + if (totalArea == 0) { this.ecosystemCarbonFlux.put(lcType, 0.0); } else { double totalFlux = 0; - double totalArea = 0; - for (LandUseTile tile : tiles) { - if (tile.isProtected()) - continue; - int age = tile.getAge(timestep); - totalFlux += cFluxes[age] * tile.getArea(); - totalArea += tile.getArea(); + for (int age=0; age<=tiles.getMaxAgeBin(); age++) { + totalFlux += cFluxes[age] * tiles.getConvertibleArea(age); } double meanFlux = totalFlux / totalArea; this.ecosystemCarbonFlux.put(lcType, meanFlux); diff --git a/src/ac/ed/lurg/country/CountryAgent.java b/src/ac/ed/lurg/country/CountryAgent.java index 8a4136c7329a300bc7f457891962c828f5a4bee2..a581b02a749a1283661b6de2d3da78b75132429f 100644 --- a/src/ac/ed/lurg/country/CountryAgent.java +++ b/src/ac/ed/lurg/country/CountryAgent.java @@ -28,7 +28,6 @@ import ac.ed.lurg.types.CommodityType; import ac.ed.lurg.types.CropType; import ac.ed.lurg.types.LandCoverType; import ac.ed.lurg.types.WoodType; -import ac.ed.lurg.utils.DoubleMap; import ac.ed.lurg.utils.LogWriter; import ac.ed.lurg.utils.cluster.Cluster; import ac.ed.lurg.utils.cluster.KMeans; @@ -78,7 +77,7 @@ public class CountryAgent extends AbstractCountryAgent { LandUseItem luItem = previousGamsRasterOutput.getLandUses().get(key); double totalLand = luItem.getTotalLandCoverArea()-luItem.getLandCoverArea(LandCoverType.URBAN); - double protectedAreaFrac = (totalLand <= 0) ? 0.0 : luItem.getProtectedArea() / totalLand; + double protectedAreaFrac = (totalLand <= 0) ? 0.0 : luItem.getTotalProtectedArea() / totalLand; clusteringPoints.add(new YieldClusterPoint(key, yieldresp, irrigItem, protectedAreaFrac)); } @@ -141,8 +140,10 @@ public class CountryAgent extends AbstractCountryAgent { if (saveGamsGdxFiles) saveGDXFile("landuse"); - + previousGamsRasterOutput = result; + + incrementLandCoverAge(); return result; } @@ -343,28 +344,29 @@ public class CountryAgent extends AbstractCountryAgent { public void updateProtectedAreas(RasterSet<LandUseItem> newProtectedAreas) { for (Entry<RasterKey, LandUseItem> entry : previousGamsRasterOutput.getLandUses().entrySet()) { - double updatedPA = newProtectedAreas.get(entry.getKey()).getProtectedArea(); - entry.getValue().setProtectedArea(updatedPA); + double updatedProtectedFract = newProtectedAreas.get(entry.getKey()).getProtectedFraction(); + entry.getValue().setProtectedFraction(updatedProtectedFract); } } private double harvestWoodFromRotation(RasterSet<WoodYieldItem> woodYieldData) { - //RasterSet<WoodYieldItem> woodYieldDataSubset = woodYieldData.createSubsetForKeys(previousGamsRasterOutput.getLandUses().keySet()); - //return woodYieldDataSubset.values().stream().mapToDouble(o -> o.getCurrentRotationHarvest()).sum(); double totalHarvest = 0; - for (Map.Entry<RasterKey, LandUseItem> entry : previousGamsRasterOutput.getLandUses().entrySet()) { - RasterKey key = entry.getKey(); - LandUseItem luItem = entry.getValue(); + for (RasterKey key : previousGamsRasterOutput.getLandUses().keySet()) { WoodYieldItem wyItem = woodYieldData.get(key); if (wyItem == null) continue; // TODO Deal with this properly totalHarvest += wyItem.getCurrentRotationHarvest(); - luItem.doForestRotation(currentTimestep, wyItem.getOptimalRotation()); // WARNING: Side effect } return totalHarvest; } + private void incrementLandCoverAge() { + for (LandUseItem luItem : previousGamsRasterOutput.getLandUses().values()) { + luItem.incrementTilesAge(); + } + } + public double getNetCarbonFlux() { return previousGamsRasterOutput.getNetCarbonFlux(); } diff --git a/src/ac/ed/lurg/country/CountryAgentManager.java b/src/ac/ed/lurg/country/CountryAgentManager.java index b5951f96b32796ee7a54174260a5f2ce125b1af2..a7408a40bddb87742396e5f9845332a2b2ef1588 100644 --- a/src/ac/ed/lurg/country/CountryAgentManager.java +++ b/src/ac/ed/lurg/country/CountryAgentManager.java @@ -26,9 +26,7 @@ import ac.ed.lurg.landuse.LandUseItem; import ac.ed.lurg.landuse.LccKey; import ac.ed.lurg.landuse.WoodUsageData; import ac.ed.lurg.types.CropType; -import ac.ed.lurg.types.LandCoverType; import ac.ed.lurg.types.WoodType; -import ac.ed.lurg.utils.DoubleMap; import ac.ed.lurg.utils.LogWriter; import ac.ed.lurg.yield.YieldRaster; import ac.sac.raster.IntegerRasterItem; diff --git a/src/ac/ed/lurg/country/LandCoverChangeItem.java b/src/ac/ed/lurg/country/LandCoverChangeItem.java index 5e06a36fc9b70419f5f9ecdc52a17f4400a08559..348b93e29e3fb72f8274bbd3c4196d185ea8b478 100644 --- a/src/ac/ed/lurg/country/LandCoverChangeItem.java +++ b/src/ac/ed/lurg/country/LandCoverChangeItem.java @@ -3,24 +3,18 @@ package ac.ed.lurg.country; import ac.ed.lurg.types.LandCoverType; public class LandCoverChangeItem { - private int location; private LandCoverType fromLandCover; private LandCoverType toLandCover; private double area; public LandCoverChangeItem() {} - public LandCoverChangeItem(int location, LandCoverType fromLandCover, LandCoverType toLandCover, double area) { - this.location = location; + public LandCoverChangeItem(LandCoverType fromLandCover, LandCoverType toLandCover, double area) { this.fromLandCover = fromLandCover; this.toLandCover = toLandCover; this.area = area; } - public int getLocation() { - return location; - } - public LandCoverType getFromLandCover() { return fromLandCover; } diff --git a/src/ac/ed/lurg/country/gams/GamsLocationInput.java b/src/ac/ed/lurg/country/gams/GamsLocationInput.java index a1e398cc91fc930827317b56a0071287b2ef1a68..211b4974c01b16c466c7d96a2003842a88085de2 100644 --- a/src/ac/ed/lurg/country/gams/GamsLocationInput.java +++ b/src/ac/ed/lurg/country/gams/GamsLocationInput.java @@ -5,12 +5,9 @@ import java.util.Map; import ac.ed.lurg.Timestep; import ac.ed.lurg.carbon.CarbonFluxItem; import ac.ed.lurg.forestry.WoodYieldData; -import ac.ed.lurg.forestry.WoodYieldItem; import ac.ed.lurg.landuse.IrrigationItem; import ac.ed.lurg.landuse.LandUseItem; import ac.ed.lurg.landuse.LccKey; -import ac.ed.lurg.types.LandCoverType; -import ac.ed.lurg.utils.DoubleMap; import ac.ed.lurg.yield.YieldResponsesItem; public class GamsLocationInput { diff --git a/src/ac/ed/lurg/country/gams/GamsLocationOptimiser.java b/src/ac/ed/lurg/country/gams/GamsLocationOptimiser.java index 0569bf7d8a0ba52d1abf3c7e687dafa7357482c2..7fe7179a11a72a738ee344610625055c40aa3545 100644 --- a/src/ac/ed/lurg/country/gams/GamsLocationOptimiser.java +++ b/src/ac/ed/lurg/country/gams/GamsLocationOptimiser.java @@ -172,7 +172,7 @@ public class GamsLocationOptimiser { Vector<String> v = new Vector<String>(); v.add(lc.getName()); v.add(locString); - setGamsParamValueTruncate(prevLandCoverP.addRecord(v), landUseItem.getUnprotectedLandCoverArea(lc), 4); + setGamsParamValueTruncate(prevLandCoverP.addRecord(v), landUseItem.getConvertibleLandCoverArea(lc), 4); } // Infrastructure cost @@ -569,15 +569,7 @@ public class GamsLocationOptimiser { double croplandArea = getParmValue(parmCroplandArea, locationName); - if (cropType.equals(CropType.PASTURE)) { - landUseItem.setLandCoverArea(LandCoverType.PASTURE, area); - totalPastureArea += area; - } - else { - landUseItem.setLandCoverArea(LandCoverType.CROPLAND, croplandArea); // will set this multiple times, once for each arable crop, but doesn't really matter - totalCropArea += area; - } - + landUseItem.setCropFraction(cropType, croplandArea > 0 ? area/croplandArea : 0); // TODO gives wrong pasture numbers } @@ -606,7 +598,7 @@ public class GamsLocationOptimiser { int locId = Integer.parseInt(locationName); double change = rec.getLevel(); - // Skip if no change + // Skip if no change or no conversion if (change == 0 || fromLcStr.equals(toLcStr)) continue; @@ -614,7 +606,7 @@ public class GamsLocationOptimiser { LandCoverType toLc = LandCoverType.getForName(toLcStr); ArrayList<LandCoverChangeItem> changesList = landCoverChanges.computeIfAbsent(locId, k -> new ArrayList<LandCoverChangeItem>()); - changesList.add(new LandCoverChangeItem(locId, fromLc, toLc, change)); + changesList.add(new LandCoverChangeItem(fromLc, toLc, change)); } // Carbon flux @@ -635,7 +627,8 @@ public class GamsLocationOptimiser { double previousNetImport = previousWoodUsageMap.get(wt).getNetImport(); double newNetImport = previousNetImport + netWoodImportChange / numWoodTypes; double previousHarvest = previousWoodUsageMap.get(wt).getHarvest(); - double newHarvest = totalWoodHarvest * (previousHarvest / previousWoodHarvestSum); + // assuming equal split if no previous harvest + double newHarvest = (previousWoodHarvestSum > 0) ? totalWoodHarvest * (previousHarvest / previousWoodHarvestSum) : totalWoodHarvest / numWoodTypes; newWoodUsageMap.put(wt, new WoodUsageData(newHarvest, newNetImport)); } diff --git a/src/ac/ed/lurg/country/gams/GamsLocationOutput.java b/src/ac/ed/lurg/country/gams/GamsLocationOutput.java index edce072f7f69549863c58b4e99b2145e888b2eaf..08bb3f85a592941e798f553c7413daea5e7eac2e 100644 --- a/src/ac/ed/lurg/country/gams/GamsLocationOutput.java +++ b/src/ac/ed/lurg/country/gams/GamsLocationOutput.java @@ -9,8 +9,6 @@ import ac.ed.lurg.landuse.CropUsageData; import ac.ed.lurg.landuse.LandUseItem; import ac.ed.lurg.landuse.WoodUsageData; import ac.ed.lurg.types.CropType; -import ac.ed.lurg.utils.TripleMap; -import ac.ed.lurg.types.LandCoverType; import ac.ed.lurg.types.WoodType; import ac.ed.lurg.country.LandCoverChangeItem; diff --git a/src/ac/ed/lurg/country/gams/GamsRasterInput.java b/src/ac/ed/lurg/country/gams/GamsRasterInput.java index 71f255e343e41e960982f244e664be809de8c08a..ddc7ee8d786d6248e0916c86f1a49d4f20943e2c 100644 --- a/src/ac/ed/lurg/country/gams/GamsRasterInput.java +++ b/src/ac/ed/lurg/country/gams/GamsRasterInput.java @@ -7,8 +7,6 @@ import ac.ed.lurg.carbon.CarbonFluxItem; import ac.ed.lurg.forestry.WoodYieldItem; import ac.ed.lurg.landuse.LandUseItem; import ac.ed.lurg.landuse.LccKey; -import ac.ed.lurg.types.LandCoverType; -import ac.ed.lurg.utils.DoubleMap; import ac.ed.lurg.landuse.IrrigationItem; import ac.ed.lurg.yield.YieldRaster; import ac.sac.raster.RasterSet; diff --git a/src/ac/ed/lurg/country/gams/GamsRasterOptimiser.java b/src/ac/ed/lurg/country/gams/GamsRasterOptimiser.java index 1f675e9a0ac394d1a1fbd975673f10a99ba2a7d0..fc6c36c4e37ff5ae8f07082bb00e78f07bb9a171 100644 --- a/src/ac/ed/lurg/country/gams/GamsRasterOptimiser.java +++ b/src/ac/ed/lurg/country/gams/GamsRasterOptimiser.java @@ -11,7 +11,6 @@ import ac.ed.lurg.carbon.CarbonFluxItem; import ac.ed.lurg.country.LandCoverChangeItem; import ac.ed.lurg.forestry.WoodYieldData; import ac.ed.lurg.forestry.WoodYieldItem; -import ac.ed.lurg.forestry.WoodYieldRasterSet; import ac.ed.lurg.landuse.Intensity; import ac.ed.lurg.landuse.IrrigationItem; import ac.ed.lurg.landuse.LandUseItem; @@ -19,7 +18,6 @@ import ac.ed.lurg.landuse.LccKey; import ac.ed.lurg.types.CropType; import ac.ed.lurg.types.LandCoverType; import ac.ed.lurg.types.YieldType; -import ac.ed.lurg.utils.DoubleMap; import ac.ed.lurg.utils.LazyTreeMap; import ac.ed.lurg.utils.LogWriter; import ac.ed.lurg.yield.YieldRaster; @@ -63,7 +61,7 @@ public class GamsRasterOptimiser { } private RasterSet<LandUseItem> createWithSameLandCovers(RasterSet<LandUseItem> toCopy) { - RasterSet<LandUseItem> theCopy = new RasterSet<LandUseItem>(toCopy.getHeaderDetails()); + RasterSet<LandUseItem> theCopy = new RasterSet<LandUseItem>(toCopy.getHeaderDetails()); // TODO why do we make a copy??? for (Entry<RasterKey, LandUseItem> entry : toCopy.entrySet()) { if (entry.getValue() != null) { @@ -82,7 +80,7 @@ public class GamsRasterOptimiser { for (LandUseItem a : areaRaster.values()) { if (a != null) { total += a.getLandCoverArea(l); - unprotected += a.getUnprotectedLandCoverArea(l); + unprotected += a.getConvertibleLandCoverArea(l); } } @@ -93,7 +91,7 @@ public class GamsRasterOptimiser { double suitableArea=0, protectedArea=0; for (LandUseItem a : areaRaster.values()) { if (a != null) { - protectedArea += a.getProtectedArea(); + protectedArea += a.getTotalProtectedArea(); suitableArea += a.getSuitableArea(); } } @@ -184,7 +182,7 @@ public class GamsRasterOptimiser { for (RasterKey key : keys) { LandUseItem newLandUseItem = newLandUseRaster.get(key); - totalUnprotectedLC += newLandUseItem.getUnprotectedLandCoverArea(fromType); + totalUnprotectedLC += newLandUseItem.getConvertibleLandCoverArea(fromType); } for (RasterKey key : keys) { @@ -192,9 +190,8 @@ public class GamsRasterOptimiser { LandUseItem newLandUseItem = newLandUseRaster.get(key); if (newLandUseItem!=null) { - double cellChange = (totalUnprotectedLC > 0) ? change * newLandUseItem.getUnprotectedLandCoverArea(fromType)/totalUnprotectedLC : change / keys.size(); + double cellChange = (totalUnprotectedLC > 0) ? change * newLandUseItem.getConvertibleLandCoverArea(fromType)/totalUnprotectedLC : change / keys.size(); double shortfall = newLandUseItem.moveAreas(toType, fromType, cellChange); - shortfall += newLandUseItem.moveUnprotectedArea(toType, fromType, cellChange, rasterInputData.getTimestep()); if (shortfall == 0) keysWithSpace.add(key); else @@ -273,7 +270,6 @@ public class GamsRasterOptimiser { IrrigationItem irrigItem = irrigRaster.get(key); WoodYieldItem woodYieldItem = woodYieldRaster.get(key); CarbonFluxItem carbonFluxItem = carbonFluxRaster.get(key); -// WoodYieldItem woodYieldItem = woodYieldRaster.get(key); int clusterId = mapping.get(key).getInt(); @@ -392,30 +388,12 @@ public class GamsRasterOptimiser { Intensity intensityThisTime = landUseItem.getIntensity(crop); aggLandUse.setIntensity(crop, intensityThisTime); // intensity currently is always the same within a location, so can just that any. If this changes need to average here. } - - // Unavailable Fraction - double unavailAreaThisTime = landUseItem.getUnavailableArea(); - double unavailAreaSoFar = aggLandUse.getUnavailableArea(); - aggLandUse.setUnavailableArea(unavailAreaSoFar + unavailAreaThisTime); - - // Protected areas - double protectedThisTime = landUseItem.getProtectedArea(); - double protectedSoFar = aggLandUse.getProtectedArea(); - aggLandUse.setProtectedArea(protectedSoFar + protectedThisTime); - - //Suitable areas - aggLandUse.setSuitableArea(suitableAreaSoFar + suitableAreaThisTime); - // Land covers areas for (LandCoverType landType : LandCoverType.values()) { - double unprotectedAreaThisTime = landUseItem.getUnprotectedLandCoverArea(landType); - double unprotectedAreaSoFar = aggLandUse.getUnprotectedLandCoverArea(landType); - aggLandUse.setUnprotectedLandCoverArea(landType, unprotectedAreaSoFar + unprotectedAreaThisTime); - - double totalAreaThisTime = landUseItem.getLandCoverArea(landType); - double totalAreaSoFar = aggLandUse.getLandCoverArea(landType); - aggLandUse.setLandCoverArea(landType, totalAreaSoFar + totalAreaThisTime); + double totalAreaThisTime = landUseItem.getConvertibleLandCoverArea(landType); + double totalAreaSoFar = aggLandUse.getConvertibleLandCoverArea(landType); + aggLandUse.setConvertibleLandCoverArea(landType, totalAreaSoFar + totalAreaThisTime); } } @@ -429,8 +407,10 @@ public class GamsRasterOptimiser { // TODO shouldn't have missing data in the first place double meanRotation = aggregatedForestRotation.values().stream().mapToDouble(o -> o.doubleValue()).sum() / aggregatedForestRotation.keySet().size(); for (Integer locId : aggregatedYields.keySet()) { - if (!aggregatedForestRotation.containsKey(locId)) + if (!aggregatedForestRotation.containsKey(locId)) { aggregatedForestRotation.put(locId, meanRotation); + LogWriter.printlnWarning("GamsRasterOptimister.convertFromRaster missing timber rotation; substituting average"); + } } return new GamsLocationInput(rasterInputData.getTimestep(), aggregatedYields, aggregatedAreas, aggregatedIrrigCosts, diff --git a/src/ac/ed/lurg/forestry/ForestManager.java b/src/ac/ed/lurg/forestry/ForestManager.java deleted file mode 100644 index 3027c3f8fbe0caddeb7c71efadac28f66a293017..0000000000000000000000000000000000000000 --- a/src/ac/ed/lurg/forestry/ForestManager.java +++ /dev/null @@ -1,77 +0,0 @@ -package ac.ed.lurg.forestry; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import ac.ed.lurg.ModelConfig; -import ac.ed.lurg.Timestep; -import ac.ed.lurg.carbon.CarbonFluxItem; -import ac.ed.lurg.landuse.LandUseTile; -import ac.ed.lurg.types.LandCoverType; -import ac.ed.lurg.utils.LogWriter; - -public class ForestManager implements Serializable { - private static final long serialVersionUID = 3093028776147903435L; - - private WoodYieldItem woodYields; - private int optimalRotation; - - public ForestManager() { - this.woodYields = new WoodYieldItem(); - } - /* - public void setWoodYield(LandCoverType forestType, int year, int age, double yield) { - woodYields.setYield(forestType, year, age, yield); - } - - public void copyWoodYields(WoodYieldItem woodYieldItem) { - woodYields.copyAll(woodYieldItem); - } - - public void calculateOptimalRotation(double woodPrice, double discountRate, Timestep timestep) { - Map<Double, Integer> levMap = new HashMap<Double, Integer>(); // Land Expected Value - int maxAge = WoodYieldItem.MAX_AGE; - int year = timestep.getYear(); - - for (int age = 0; age <= maxAge; age++) { - double yield = woodYields.getYield(LandCoverType.TIMBER_FOREST, year, age); - double lev = (woodPrice * yield * Math.exp(-discountRate * age) - ModelConfig.FOREST_ESTABLISHMENT_COST) / (1 - Math.exp(-discountRate * age)); - levMap.put(lev, age); - } - // TODO potential edge case with tied LEV - double maxLev = Collections.max(levMap.keySet()); - optimalRotation = levMap.get(maxLev); - } - - public int getOptimalRotation() { - return optimalRotation; - } - - public double getStandYield(LandUseTile tile, Timestep timestep) { - return woodYields.getYield(tile.getLcType(), timestep.getYear(), tile.getAge(timestep)); - } - - public double getAverageYield(ArrayList<LandUseTile> forestTiles, Timestep timestep) { - if (forestTiles == null) - return 0; - Double totalWoodStock = forestTiles.stream().filter(o -> !o.isProtected()).mapToDouble(o -> o.getArea() * getStandYield(o, timestep)).sum(); - if (totalWoodStock > 0) { - int foo = 1; - } - double totalArea = forestTiles.stream().filter(o -> !o.isProtected()).mapToDouble(o -> o.getArea()).sum(); - if (totalWoodStock == null || totalWoodStock == 0) { - return 0.0; - } else { - return totalWoodStock / totalArea; - } - } - - public double getYieldAtRotation(int year) { - return woodYields.getYield(LandCoverType.TIMBER_FOREST, year, optimalRotation); - } - */ - -} diff --git a/src/ac/ed/lurg/forestry/WoodYieldItem.java b/src/ac/ed/lurg/forestry/WoodYieldItem.java index 608ee0aa47b126655db735099c4d33711ce5bde3..e0de28c42576327e1236ee0aecab9198e3b84b4a 100644 --- a/src/ac/ed/lurg/forestry/WoodYieldItem.java +++ b/src/ac/ed/lurg/forestry/WoodYieldItem.java @@ -3,14 +3,14 @@ package ac.ed.lurg.forestry; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.stream.Collectors; - import ac.ed.lurg.ModelConfig; import ac.ed.lurg.Timestep; -import ac.ed.lurg.landuse.LandUseTile; +import ac.ed.lurg.landuse.LandCoverTile; import ac.ed.lurg.landuse.LccKey; import ac.ed.lurg.types.LandCoverType; +import ac.ed.lurg.utils.LogWriter; import ac.sac.raster.RasterItem; public class WoodYieldItem implements RasterItem { @@ -29,25 +29,21 @@ public class WoodYieldItem implements RasterItem { public WoodYieldItem() {} - public void calcYieldData(Map<LccKey, Double[]> woodYields, Map<LandCoverType, ArrayList<LandUseTile>> landUseTiles, Timestep timestep) { + public void calcYieldData(Map<LccKey, Double[]> woodYields, Map<LandCoverType, LandCoverTile> landUseTiles, Timestep timestep) { // Mean wood yield for grid cell for (Map.Entry<LccKey, Double[]> entry : woodYields.entrySet()) { LccKey key = entry.getKey(); Double[] yields = entry.getValue(); - ArrayList<LandUseTile> tiles = landUseTiles.get(key.getFromLc()); + LandCoverTile tiles = landUseTiles.get(key.getFromLc()); - double totalArea = tiles.stream().filter(o -> !o.isProtected()).mapToDouble(o -> o.getArea()).sum(); - if (tiles.size() == 0 || totalArea == 0) { + double totalArea = tiles.getTotalConvertibleArea(); // Assuming no harvest from protected areas + if (totalArea == 0) { this.yield.put(key, 0.0) ; } else { double totalYield = 0; - for (LandUseTile tile : tiles) { - if (tile.isProtected()) - continue; - int age = tile.getAge(timestep); - totalYield += yields[age] * tile.getArea(); - totalArea += tile.getArea(); + for (int age=0; age<=tiles.getMaxAgeBin(); age++) { + totalYield += yields[age] * tiles.getConvertibleArea(age); } double meanYield = totalYield / totalArea; @@ -56,31 +52,45 @@ public class WoodYieldItem implements RasterItem { } } - public void calcRotationData(Map<LccKey, Double[]> woodYields, Map<LandCoverType, ArrayList<LandUseTile>> landUseTiles, Timestep timestep, double woodPrice) { + public void calcRotationData(Map<LccKey, Double[]> woodYields, Map<LandCoverType, LandCoverTile> landUseTiles, Timestep timestep, double woodPrice) { // Optimal rotation - Map<Double, Integer> levMap = new HashMap<Double, Integer>(); // Land Expected Value + List<Double> levArr = new ArrayList<Double>(); Double[] yields = woodYields.get(new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.TIMBER_FOREST)); for (int age = 0; age <= WoodYieldItem.MAX_AGE; age++) { double yield = yields[age]; double lev = (woodPrice * yield * Math.exp(-ModelConfig.DISCOUNT_RATE * age) - ModelConfig.FOREST_ESTABLISHMENT_COST) / (1 - Math.exp(-ModelConfig.DISCOUNT_RATE * age)); - levMap.put(lev, age); + levArr.add(lev); + } + + double maxLev = Collections.max(levArr); + List<Integer> candidates = new ArrayList<Integer>(); // There may be several equal maximum values + for (int i = 0; i < levArr.size(); i ++) { + if (levArr.get(i) == maxLev) { + candidates.add(i); + } + } + + try { + optimalRotation = Collections.min(candidates); // Choose shortest rotation + } catch(Exception e) { + LogWriter.print(e); } - // TODO potential edge case with tied LEV - double maxLev = Collections.max(levMap.keySet()); - optimalRotation = levMap.get(maxLev); yieldAtRotation = yields[optimalRotation]; - ArrayList<LandUseTile> timberTiles = landUseTiles.get(LandCoverType.TIMBER_FOREST); - if (timberTiles.size() == 0) { + LandCoverTile timberTiles = landUseTiles.get(LandCoverType.TIMBER_FOREST); + double timberForestArea = timberTiles.getTotalConvertibleArea(); + if (timberForestArea == 0) { currentRotationHarvest = 0.0; } else { - double areaHarvested = timberTiles.stream() - .filter(o -> !o.isProtected() && o.getAge(timestep) >= optimalRotation) - .mapToDouble(o -> o.getArea()) - .sum(); - currentRotationHarvest = areaHarvested * yieldAtRotation; + double areaHarvested = 0; + for (int age=optimalRotation; age<=timberTiles.getMaxAgeBin(); age++) { + areaHarvested += timberTiles.getConvertibleArea(age); + timberTiles.resetAreaForAge(age); // Warning side effect + } + currentRotationHarvest = areaHarvested * yieldAtRotation; // TODO how to better deal with age > rotationAge at initialisation? + } } diff --git a/src/ac/ed/lurg/landuse/InitProtectedAreasReader.java b/src/ac/ed/lurg/landuse/InitProtectedAreasReader.java new file mode 100644 index 0000000000000000000000000000000000000000..24774e431c12c18b7fe50c6e5c3e859588454a71 --- /dev/null +++ b/src/ac/ed/lurg/landuse/InitProtectedAreasReader.java @@ -0,0 +1,18 @@ +package ac.ed.lurg.landuse; + +import ac.sac.raster.AbstractRasterReader; +import ac.sac.raster.RasterSet; + +public class InitProtectedAreasReader extends AbstractRasterReader<LandCoverItem> { + public InitProtectedAreasReader (RasterSet<LandCoverItem> dataset) { + super(dataset); + } + + @Override + public void setData(LandCoverItem lcData, String token) { + if (!"nan".equals(token)) { + double protFrac = Double.parseDouble(token); + lcData.setProtectedFraction(protFrac); + } + } +} diff --git a/src/ac/ed/lurg/landuse/LandCoverItem.java b/src/ac/ed/lurg/landuse/LandCoverItem.java index 767624f570982ba46ce9cc4585a52000cc3be6a8..66936e434312e4b26863bc2467842d713928c1a3 100644 --- a/src/ac/ed/lurg/landuse/LandCoverItem.java +++ b/src/ac/ed/lurg/landuse/LandCoverItem.java @@ -1,6 +1,5 @@ package ac.ed.lurg.landuse; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -11,11 +10,12 @@ import ac.sac.raster.RasterItem; * This is used in the initalisation phase, after that land-use is used */ public class LandCoverItem implements RasterItem { - private Map<LandCoverType, Double> landcover = new HashMap<LandCoverType, Double>(); + private Map<LandCoverType, Double> landcover = new HashMap<LandCoverType, Double>(); // Land cover fraction private Map<LandCoverType, Map<Integer, Double>> landUseAgeDist = new HashMap<LandCoverType, Map<Integer, Double>>(); private double totalArea; private double unavailableFract; // due to slope private double initialForestedNaturalFract; + private double protectedFraction; /** Area in Mha */ public Double getLandCoverArea(LandCoverType landType) { @@ -27,6 +27,10 @@ public class LandCoverItem implements RasterItem { return d==null ? 0 : d.doubleValue(); } + public Map<LandCoverType, Double> getLandCoverMap() { + return landcover; + } + /** Area in Mha */ public double getTotalArea() { return totalArea; @@ -65,4 +69,12 @@ public class LandCoverItem implements RasterItem { public Map<LandCoverType, Map<Integer, Double>> getLandUseAgeDist() { return landUseAgeDist; } + + public double getProtectedFraction() { + return protectedFraction; + } + + public void setProtectedFraction(double protectedFraction) { + this.protectedFraction = protectedFraction; + } } \ No newline at end of file diff --git a/src/ac/ed/lurg/landuse/LandCoverTile.java b/src/ac/ed/lurg/landuse/LandCoverTile.java new file mode 100644 index 0000000000000000000000000000000000000000..3f0bdb383a441ced8b12fc1e140aa717f09d79b7 --- /dev/null +++ b/src/ac/ed/lurg/landuse/LandCoverTile.java @@ -0,0 +1,156 @@ +package ac.ed.lurg.landuse; + +import java.io.Serializable; +import java.util.Arrays; + +import ac.ed.lurg.utils.LogWriter; +import ac.sac.raster.InterpolatingRasterItem; + +public class LandCoverTile implements Serializable, InterpolatingRasterItem<LandCoverTile> { + private static final long serialVersionUID = 3208516181615267472L; + private static final int MAX_AGE = 250; + + private Double[] convertibleAreas; // area Mha; index = age + private Double[] protectedAreas; // area Mha; index = age + private double unavailableArea; // unavailable for conversion due to slope, altitude TODO age? + + public LandCoverTile() { + this.convertibleAreas = new Double[MAX_AGE+1]; + this.protectedAreas = new Double[MAX_AGE+1]; + Arrays.fill(convertibleAreas, 0.0); + Arrays.fill(protectedAreas, 0.0); + unavailableArea = 0.0; + } + + public double getConvertibleArea(int age) { + return convertibleAreas[age]; + } + + public void setConvertibleArea(int age, double area) { + convertibleAreas[age] = area; + } + + public void addConvertibleArea(double a) { + convertibleAreas[0] += a; + } + + public double getTotalConvertibleArea() { + double total = 0; + for (double d : convertibleAreas) { + total += d; + } + return total; + } + + public void removeConvertibleArea(double a) { // Subtracts area uniformly + if (a <= 0) + return; + double totalArea = getTotalConvertibleArea(); + double areaToRemove = a; + if (a > totalArea) { + LogWriter.printlnError("LandUseTile.removeConvertibleArea: attempting to remove too much area: " + a + ", available: " + totalArea); + areaToRemove = totalArea; + } + double factor = (totalArea - areaToRemove) / totalArea; + for (int i=0; i<convertibleAreas.length; i++) { + convertibleAreas[i] *= factor; + } + } + + public void setProtectedArea(int age, double area) { + protectedAreas[age] = area; + } + + public void addProtectedArea(double d) { + protectedAreas[0] += d; + + } + + public double getTotalProtectedArea() { + double total = 0; + for (double d : protectedAreas) { + total += d; + } + return total; + } + + public void removeProtectedArea(double a) { // Subtracts area uniformly + if (a <= 0) + return; + double totalArea = getTotalProtectedArea(); + double areaToRemove = a; + if (a > totalArea) { + LogWriter.printlnError("LandUseTile.removeProtectedArea: attempting to remove too much area: " + a + ", available: " + totalArea); + areaToRemove = totalArea; + } + double factor = (totalArea - areaToRemove) / totalArea; + for (int i=0; i<protectedAreas.length; i++) { + protectedAreas[i] *= factor; + } + } + + public void addUnavailableArea(double d) { + unavailableArea += d; + } + + public double getTotalArea() { + return getTotalConvertibleArea() + getTotalProtectedArea() + unavailableArea; + } + + public void resetAreaForAge(int age) { + convertibleAreas[0] += convertibleAreas[age]; + convertibleAreas[age] = 0.0; + } + + public void removeAllArea() { + Arrays.fill(convertibleAreas, 0.0); + Arrays.fill(protectedAreas, 0.0); + unavailableArea = 0.0; + } + + public void incrementAge() { + int n = convertibleAreas.length; + convertibleAreas[n-1] += convertibleAreas[n-2]; // final age bin is all absorbing + convertibleAreas[n-2] = 0.0; + // shift values + for (int i=(n-2); i>0; i--) { + convertibleAreas[i] = convertibleAreas[i-1]; + } + convertibleAreas[0]= 0.0; + + int m = protectedAreas.length; + protectedAreas[m-1] += protectedAreas[m-2]; // final age bin is all absorbing + protectedAreas[m-2] = 0.0; + // shift values + for (int i=(m-2); i>0; i--) { + protectedAreas[i] = protectedAreas[i-1]; + } + protectedAreas[0]= 0.0; + } + + // Moves area from convertible to protected, maintaining age classes + public void moveAreaToProtected(double area) { + double convertibleA = getTotalConvertibleArea(); + if (area > convertibleA) { + LogWriter.printlnWarning("LandUseTile.moveAreaToProtected cannot protect all required area"); + area = Math.min(convertibleA, area); + } + double fractToMove = area / convertibleA; + for (int i = 0; i <= MAX_AGE; i ++) { + double areaToMove = convertibleAreas[i] * fractToMove; + protectedAreas[i] += areaToMove; + convertibleAreas[i] -= areaToMove; + } + } + + public int getMaxAgeBin() { + return MAX_AGE; + } + + @Override + public void interpolateAll(LandCoverTile fromItem, LandCoverTile toItem, double factor) { + // TODO Auto-generated method stub + + } + +} diff --git a/src/ac/ed/lurg/landuse/LandUseItem.java b/src/ac/ed/lurg/landuse/LandUseItem.java index a378194e4b51e8f95c94497a940479d9ee4eaeed..a00e2419afb29be03e914366acd2ffb7b056de39 100644 --- a/src/ac/ed/lurg/landuse/LandUseItem.java +++ b/src/ac/ed/lurg/landuse/LandUseItem.java @@ -1,17 +1,10 @@ package ac.ed.lurg.landuse; import java.io.Serializable; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; - import ac.ed.lurg.ModelConfig; -import ac.ed.lurg.Timestep; -import ac.ed.lurg.forestry.WoodYieldItem; -import ac.ed.lurg.forestry.ForestManager; import ac.ed.lurg.types.CropToDouble; import ac.ed.lurg.types.CropType; import ac.ed.lurg.types.LandCoverType; @@ -24,27 +17,24 @@ public class LandUseItem implements InterpolatingRasterItem<LandUseItem>, Serial private Map<CropType, Intensity> intensityMap = new HashMap<CropType, Intensity>(); private Map<CropType, Double> cropFractions = new HashMap<CropType, Double>(); - private Map<LandCoverType, Double> landCoverAreas = new HashMap<LandCoverType, Double>(); //Mha - private Map<LandCoverType, Double> unprotectedAreas = new HashMap<LandCoverType, Double>(); //Mha - private Map<LandCoverType, ArrayList<LandUseTile>> landUseTiles = new HashMap<LandCoverType, ArrayList<LandUseTile>>(); - private double protectedArea; //protected area in Mha - private double unavailableArea; //area unavailable due to altitude etc - private double suitableArea; //Mha + private Map<LandCoverType, LandCoverTile> landCoverAreas = new HashMap<LandCoverType, LandCoverTile>(); + private double protectedFraction; // protected fraction of total area - public LandUseItem() {} + public LandUseItem() { + for (LandCoverType lcType : LandCoverType.values()) { + landCoverAreas.put(lcType, new LandCoverTile()); + } + } public LandUseItem(LandCoverItem landCover) { this(); if (landCover != null) { - for (LandCoverType lcType : LandCoverType.values()) - landCoverAreas.put(lcType, landCover.getLandCoverArea(lcType)); // Converts land cover fractions to areas - setCropFraction(CropType.WHEAT, 0.5); // random start as don't have better data setCropFraction(CropType.MAIZE, 0.5); + setProtectedFraction(landCover.getProtectedFraction()); + addLandCoverTiles(landCover); setUnavailableArea(landCover.getUnavailableFract()); - updateSuitableArea(ModelConfig.BASE_YEAR); - addLandUseTiles(landCover.getLandUseAgeDist()); // Should only be run after unprotected areas calculated - runAreaCheck(); // check that tile areas add up to total areas + } } @@ -53,80 +43,42 @@ public class LandUseItem implements InterpolatingRasterItem<LandUseItem>, Serial intensityMap.putAll(luItemToCopy.intensityMap); cropFractions.putAll(luItemToCopy.cropFractions); landCoverAreas.putAll(luItemToCopy.landCoverAreas); - unprotectedAreas.putAll(luItemToCopy.unprotectedAreas); - landUseTiles.putAll(luItemToCopy.landUseTiles); - protectedArea = (luItemToCopy.protectedArea); - suitableArea = (luItemToCopy.suitableArea); + protectedFraction = (luItemToCopy.protectedFraction); } - - - public void moveTilesArea(LandCoverType toType, LandCoverType fromType, double area, Timestep timestep) { - // Note if toType == fromType, area will be removed from fromType and a new tile will be created with age 0 - if (area == 0) - return; - - ArrayList<LandUseTile> toTiles = landUseTiles.get(toType); - ArrayList<LandUseTile> fromTiles = landUseTiles.get(fromType); - - if (fromTiles == null) { - LogWriter.printlnError("LandUseItem: cannot move area between tiles as source is null"); - return; + public void addLandCoverTiles(LandCoverItem landCover) { + double protectableFraction = 0; + for (LandCoverType lcType : LandCoverType.getProtectibleTypes()) { + protectableFraction += landCover.getLandCoverFract(lcType); } - - // Remove area from tiles - double totalCurrentArea = fromTiles.stream().filter(o -> !o.isProtected()).mapToDouble(o -> o.getArea()).sum(); - for (LandUseTile s: fromTiles) { - if (s.isProtected()) + // need to scale protectedFraction by protectableFraction and cap at 1.0. Gives fraction of protectable LC types to protect + double adjProtectedFraction = Math.min(1.0, protectedFraction / protectableFraction); + + for (LandCoverType lcType : LandCoverType.values()) { + LandCoverTile tiles = landCoverAreas.get(lcType); + double totalArea = landCover.getLandCoverArea(lcType); + if (totalArea == 0) continue; - double areaToRemove = s.getArea() / totalCurrentArea * area; - s.removeArea(areaToRemove); - } - - // Add new tiles - LandUseTile newStand = new LandUseTile(toType, area, timestep.getYear(), false); - toTiles.add(newStand); - - runAreaCheck(); - } - - public void runAreaCheck() { - double threshold = 1e-10; - for (Map.Entry<LandCoverType, ArrayList<LandUseTile>> entry : landUseTiles.entrySet()) { - LandCoverType lcType = entry.getKey(); - ArrayList<LandUseTile> tiles = entry.getValue(); - double totalArea = tiles.stream().mapToDouble(o -> o.getArea()).sum(); - double totalUnprotectedArea = tiles.stream().filter(o -> !o.isProtected()).mapToDouble(o -> o.getArea()).sum(); - if (totalArea - landCoverAreas.get(lcType) > threshold) { - LogWriter.printlnError("LandUseItem: total area and tiles area different: " + lcType.getName() + " " + landCoverAreas.get(lcType) + ", " + totalArea); + // static land covers + if (!lcType.isConvertible()) { + tiles.addConvertibleArea(totalArea); // Assumes land cover age is 0 + continue; } - if (totalUnprotectedArea - unprotectedAreas.get(lcType) > threshold) { - LogWriter.printlnError("LandUseItem: unprotected area and tiles area different: " + lcType.getName() + " " + unprotectedAreas.get(lcType) + ", " + totalUnprotectedArea); + // convertible land covers + double protectedArea; + double unprotectedArea; + if (lcType.isProtectable()) { + protectedArea = totalArea * adjProtectedFraction; + unprotectedArea = totalArea * (1 - adjProtectedFraction); + } else { + protectedArea = 0; + unprotectedArea = totalArea; } - } - } - - public void addLandUseTiles(Map<LandCoverType, Map<Integer, Double>> ageDist) { - - // Initialise array lists - for (LandCoverType lcType : LandCoverType.getConvertibleTypes()) { - ArrayList<LandUseTile> tiles = new ArrayList<LandUseTile>(); - landUseTiles.put(lcType, tiles); - } - - // Add tiles - for (Map.Entry<LandCoverType, Map<Integer, Double>> lcEntry : ageDist.entrySet()) { - LandCoverType lcType = lcEntry.getKey(); - if (landCoverAreas.get(lcType) == 0) - continue; - - double protectedArea = landCoverAreas.get(lcType) - unprotectedAreas.get(lcType); - double unprotectedArea = unprotectedAreas.get(lcType); - Map<Integer, Double> ageMap = lcEntry.getValue(); + + Map<Integer, Double> ageMap = landCover.getLandUseAgeDist().get(lcType); double totalProp = ageMap.values().stream().reduce(0.0, Double::sum); - if (totalProp == 0.0) { - // If no distribution given then assume land is in oldest age group + if (totalProp == 0.0) { // If no distribution given then assume land is in oldest age group int maxAgeGroup = ageMap.keySet().stream().max(Integer::compare).get(); ageMap.put(maxAgeGroup, 1.0); } @@ -136,7 +88,7 @@ public class LandUseItem implements InterpolatingRasterItem<LandUseItem>, Serial ageMap.put(entry.getKey(), entry.getValue() / totalProp); } } - + // Assuming land use distributed evenly in each age group for (Map.Entry<Integer, Double> ageEntry : ageMap.entrySet()) { double prop = ageEntry.getValue() / 10; @@ -146,80 +98,208 @@ public class LandUseItem implements InterpolatingRasterItem<LandUseItem>, Serial int minAge = (int) (ageGroup - 1) * 10 + 1; int maxAge = minAge + 9; for (int a = minAge; a <= maxAge; a++) { - int yearEstablished = ModelConfig.BASE_YEAR - a; - - LandUseTile unprotectedTile = new LandUseTile(lcType, unprotectedArea * prop, yearEstablished, false); - LandUseTile protectedTile = new LandUseTile(lcType, protectedArea * prop, yearEstablished, true); - - landUseTiles.get(lcType).add(unprotectedTile); - landUseTiles.get(lcType).add(protectedTile); + landCoverAreas.get(lcType).setConvertibleArea(a, unprotectedArea * prop); + landCoverAreas.get(lcType).setProtectedArea(a, protectedArea * prop); } } - } + } + + // Check that areas have been allocated correctly + Map<LandCoverType, Double> initAreas = new HashMap<LandCoverType, Double>(); + for (LandCoverType lc : LandCoverType.values()) { + initAreas.put(lc, landCover.getLandCoverArea(lc)); + } + runAreaCheck(initAreas); } - public Map<LandCoverType, Set<Integer>> getTilesAllYearsEstablished() { - Map<LandCoverType, Set<Integer>> reqData = new HashMap<LandCoverType, Set<Integer>>(); - for (Map.Entry<LandCoverType, ArrayList<LandUseTile>> entry : landUseTiles.entrySet()) { + + public void runAreaCheck(Map<LandCoverType, Double> lcAreas) { + double threshold = 1e-10; + for (Map.Entry<LandCoverType, LandCoverTile> entry : landCoverAreas.entrySet()) { LandCoverType lcType = entry.getKey(); - Set<Integer> yearList = new HashSet<Integer>(); // <yearEstablished> - ArrayList<LandUseTile> tiles = entry.getValue(); - if (tiles.isEmpty()) - continue; - for (LandUseTile t : tiles) { - yearList.add(t.getYearEstablished()); + LandCoverTile tiles = entry.getValue(); + double totalArea = tiles.getTotalConvertibleArea(); + if (totalArea - lcAreas.get(lcType) > threshold) { + LogWriter.printlnError("LandUseItem: unprotected area and tiles area different: " + lcType.getName() + " " + lcAreas.get(lcType) + ", " + totalArea); } - reqData.put(lcType, yearList); } - return reqData; - } - - - public Map<LandCoverType, ArrayList<LandUseTile>> getLandUseTiles() { - return landUseTiles; } - public void setProtectedFract(double protFrac) { - if (!Double.isNaN(ModelConfig.CONSTANT_PROTECTED_AREA_RATE)) - protFrac=ModelConfig.CONSTANT_PROTECTED_AREA_RATE; + public void setUnavailableArea(double unavailF) { + double barrenAndUrban = getLandCoverArea(LandCoverType.BARREN) + getLandCoverArea(LandCoverType.URBAN); + double unavailableArea = unavailF * getTotalLandCoverArea(); + double shortfall = Math.max(0.0, unavailableArea - barrenAndUrban); + if (shortfall == 0.0) + return; + + double convertibleNatural = getConvertibleLandCoverArea(LandCoverType.NATURAL); + double protectedNatural = getProtectedLandCoverArea(LandCoverType.NATURAL); + double convertibleTimberF = getConvertibleLandCoverArea(LandCoverType.TIMBER_FOREST); + /* + Map<LandCoverType, Double> foo = getLandCoverAreaMap(); + for (Map.Entry<LandCoverType, Double> entry : foo.entrySet()) { + LogWriter.println(entry.getKey().getName() + "," + entry.getValue()); + } + */ + double unavailFromConvertibleNatural = Math.min(shortfall, convertibleNatural); + shortfall -= unavailFromConvertibleNatural; + if (unavailFromConvertibleNatural > 0) { + landCoverAreas.get(LandCoverType.NATURAL).addUnavailableArea(unavailFromConvertibleNatural); + landCoverAreas.get(LandCoverType.NATURAL).removeConvertibleArea(unavailFromConvertibleNatural); + } - double totalArea = getTotalLandCoverArea(); - double urbanArea = getLandCoverArea(LandCoverType.URBAN); - double barrenArea = getLandCoverArea(LandCoverType.BARREN); + double unavailFromProtectedNatural = Math.min(shortfall, protectedNatural); + shortfall -= unavailFromProtectedNatural; + if (unavailFromProtectedNatural > 0) { + landCoverAreas.get(LandCoverType.NATURAL).addUnavailableArea(unavailFromProtectedNatural); + landCoverAreas.get(LandCoverType.NATURAL).removeProtectedArea(unavailFromProtectedNatural); + } - this.protectedArea = Math.min(totalArea-urbanArea-barrenArea, protFrac*(totalArea-urbanArea)); + double unavailFromTimberF = Math.min(shortfall, convertibleTimberF); + shortfall -= unavailFromTimberF; + if (unavailFromTimberF > 0) { + landCoverAreas.get(LandCoverType.TIMBER_FOREST).addUnavailableArea(unavailFromTimberF); + landCoverAreas.get(LandCoverType.TIMBER_FOREST).removeConvertibleArea(unavailFromTimberF); + } + if (shortfall > 0) { + LogWriter.printlnWarning("LandUseItem.setUnavailableArea insufficient area to make unavailable"); + } + } + + public Map<LandCoverType, LandCoverTile> getLandUseTiles() { + return landCoverAreas; } + + public void moveTilesArea(LandCoverType toType, LandCoverType fromType, double area) { + // Note if toType == fromType, area will be removed from fromType and added back in with age 0 + if (area == 0) + return; - public void setProtectedArea(double protectedArea) { - this.protectedArea = protectedArea; + LandCoverTile toTiles = landCoverAreas.get(toType); + LandCoverTile fromTiles = landCoverAreas.get(fromType); + + if (fromTiles == null) { + LogWriter.printlnError("LandUseItem: cannot move area between tiles as source is null"); + return; + } + + fromTiles.removeConvertibleArea(area); + toTiles.addConvertibleArea(area); + } + + public void setProtectedFraction(double protFrac) { + if (!ModelConfig.PROTECTED_AREAS_ENABLED) { + this.protectedFraction = 0; + return; + } + if (!Double.isNaN(ModelConfig.CONSTANT_PROTECTED_AREA_RATE)) { + this.protectedFraction = ModelConfig.CONSTANT_PROTECTED_AREA_RATE; // TODO is this a fraction or area? + } else { + this.protectedFraction = Math.max(protFrac, ModelConfig.MIN_NATURAL_RATE); + } } - public double getProtectedArea() { + public double getProtectedFraction() { + return protectedFraction; + } + + public double getTotalProtectedArea() { if(ModelConfig.PROTECTED_AREAS_ENABLED) - return protectedArea; + return protectedFraction * getTotalLandCoverArea(); else return 0.0; } - public void setSuitableArea(double suitable) { - this.suitableArea = suitable; + public double getProtectedLandCoverArea(LandCoverType lcType) { + return landCoverAreas.get(lcType).getTotalProtectedArea(); } - + public double getSuitableArea() { + double suitableArea = 0; + for (LandCoverType lcType : LandCoverType.getConvertibleTypes()) { + suitableArea += getConvertibleLandCoverArea(lcType); + } return suitableArea; } + /** move areas from one land cover to another, return any residual not possible */ + public double moveAreas(LandCoverType toType, LandCoverType fromType, double area) { + + double prevFrom = getConvertibleLandCoverArea(fromType); + double areaMoved; + + areaMoved = Math.min(area, prevFrom); + + if (Math.abs(area - areaMoved) / area > 1e-6) { + throw new RuntimeException("Move unprotected area shortfall. Should not happen due to GAMS constraint."); + } + + moveTilesArea(toType, fromType, areaMoved); + + return area - areaMoved; + } - public void setUnavailableArea(double unavailF) { - // subtract BARREN from unavailableArea, set to 0 if negative - unavailableArea = Math.max(unavailF*getTotalLandCoverArea() - getLandCoverArea(LandCoverType.BARREN), 0.0); + public double getLandCoverArea(LandCoverType c) { + Double d = landCoverAreas.get(c).getTotalArea(); + return d == null ? 0.0 : d; } - public double getUnavailableArea() { - return unavailableArea; + public double getLandCoverFract(LandCoverType c) { + double totalArea = getTotalLandCoverArea(); + return totalArea==0 ? 0.0 : getLandCoverArea(c) / totalArea; } + + public void setConvertibleLandCoverArea(LandCoverType c, double d) { + if (Double.isNaN(d) || Double.isInfinite(d)) + throw new RuntimeException("AreasItem for " + c + " is " + d); + double landCover = (d < 0.0) ? 0.0 : d; + + landCoverAreas.get(c).removeAllArea(); + landCoverAreas.get(c).addConvertibleArea(landCover);; + } + + public double getTotalLandCoverArea() { + double d = 0; + for (LandCoverType l : LandCoverType.values()) + d += getLandCoverArea(l); + + return d; + } + + public Map<LandCoverType, Double> getLandCoverAreaMap() { + Map<LandCoverType, Double> areaMap = new HashMap<LandCoverType, Double>(); + for (LandCoverType lcType : LandCoverType.values()) { + areaMap.put(lcType, getLandCoverArea(lcType)); + } + return areaMap; + } + + public double getTotalConvertibleNaturalArea() { + double unprotectedNatural = 0; + for (LandCoverType landType : LandCoverType.getNaturalTypes()) { + unprotectedNatural += landCoverAreas.get(landType).getTotalConvertibleArea(); + } + return unprotectedNatural; + } + + public double getConvertibleLandCoverArea(LandCoverType c) { + return landCoverAreas.get(c).getTotalConvertibleArea(); + } + + + public void updateProtectedFraction(int year) { + if (!ModelConfig.FORCE_PROTECTED_AREAS) + return; + + if (year >= ModelConfig.FORCE_PROTECTED_AREAS_START_YEAR && year < ModelConfig.FORCE_PROTECTED_AREAS_END_YEAR) + protectedFraction = 1.0 / (ModelConfig.FORCE_PROTECTED_AREAS_END_YEAR - year); + + if (year >= ModelConfig.FORCE_PROTECTED_AREAS_END_YEAR) + protectedFraction = 1.0; + } + public Intensity getIntensity(CropType crop) { return intensityMap.get(crop); } @@ -321,56 +401,6 @@ public class LandUseItem implements InterpolatingRasterItem<LandUseItem>, Serial public static double getIrrigationTotal(Collection<? extends LandUseItem> items, Collection<CropType> crops) { return getIrrigationTotal(items, crops.toArray(new CropType[crops.size()])); } - - /** move areas from one land cover to another, return any residual not possible */ - public double moveAreas(LandCoverType toType, LandCoverType fromType, double changeReq) { - - if (fromType.equals(toType)) - return 0; - - double prevTo = getLandCoverArea(toType); - double prevFrom = getLandCoverArea(fromType); - - double actuallyChanged = Math.min(prevFrom, changeReq); - - /* - if (changeReq - actuallyChanged > 0) { - throw new RuntimeException("Move area shortfall. Should not happen due to GAMS constraint."); - }*/ - - setLandCoverArea(toType, prevTo + actuallyChanged); - setLandCoverArea(fromType, prevFrom - actuallyChanged); - - return changeReq - actuallyChanged; - } - - public double moveUnprotectedArea(LandCoverType toType, LandCoverType fromType, double area, Timestep timestep) { - - double prevTo = getUnprotectedLandCoverArea(toType); - double prevFrom = getUnprotectedLandCoverArea(fromType); - double areaMoved; - - areaMoved = Math.min(area, prevFrom); - - if (!fromType.equals(toType)) { - unprotectedAreas.put(toType, prevTo + areaMoved); - unprotectedAreas.put(fromType, prevFrom - areaMoved); - } - - /* - if (area - areaMoved > 0) { - throw new RuntimeException("Move unprotected area shortfall. Should not happen due to GAMS constraint."); - } - */ - - double foo = landUseTiles.get(fromType).stream().filter(o -> !o.isProtected()).mapToDouble(o -> o.getArea()).sum(); - double foo2 = landUseTiles.get(fromType).stream().mapToDouble(o -> o.getArea()).sum(); - - moveTilesArea(toType, fromType, areaMoved, timestep); - - - return area - areaMoved; - } public double getCropArea(CropType c) { Double d = getLandCoverArea(LandCoverType.CROPLAND) * getCropFraction(c); @@ -390,38 +420,10 @@ public class LandUseItem implements InterpolatingRasterItem<LandUseItem>, Serial return totalFract; } - public void setCropFraction(CropType c, double areaFract) { cropFractions.put(c, areaFract); } - - public double getLandCoverArea(LandCoverType c) { - Double d = landCoverAreas.get(c); - return d == null ? 0.0 : d; - } - - public double getLandCoverFract(LandCoverType c) { - double totalArea = getTotalLandCoverArea(); - return totalArea==0 ? 0.0 : getLandCoverArea(c) / totalArea; - } - - public void setLandCoverArea(LandCoverType c, double d) { - if (Double.isNaN(d) || Double.isInfinite(d)) - throw new RuntimeException("AreasItem for " + c + " is " + d); - double landCover = (d < 0.0) ? 0.0 : d; - - landCoverAreas.put(c, landCover); - } - - public double getTotalLandCoverArea() { - double d = 0; - for (LandCoverType l : LandCoverType.values()) - d += getLandCoverArea(l); - - return d; - } - public static double getAbandonedPasture(Collection<? extends LandUseItem> landUses) { double total = 0; for (LandUseItem a : landUses) { @@ -445,81 +447,7 @@ public class LandUseItem implements InterpolatingRasterItem<LandUseItem>, Serial } return total; } - - public double getTotalNatural() { - double totalNatural = getLandCoverArea(LandCoverType.NATURAL); - return totalNatural; - } - - public double getTotalUnprotectedNatural() { - double unprotectedNatural = 0; - for (LandCoverType landType : LandCoverType.values()) { - if (landType.isNatural()) { - unprotectedNatural += unprotectedAreas.get(landType); - } - } - return unprotectedNatural; - } - - public double getUnprotectedLandCoverArea(LandCoverType landType) { - Double d = unprotectedAreas.get(landType); - return d == null ? 0.0 : d; - } - - public void setUnprotectedLandCoverArea(LandCoverType landType, double d) { - unprotectedAreas.put(landType, d); - - } - - private double getProtectedandUnavailable() { - double totalSuitableLandCover = getTotalLandCoverArea() - getLandCoverArea(LandCoverType.BARREN) - getLandCoverArea(LandCoverType.URBAN); - if (totalSuitableLandCover < 0) - throw new RuntimeException("totalSuitableLandCover < 0"); - //return 0; - // Protected area should be at least MIN_NATURAL_RATE - double desiredProtectedArea = Math.max(getProtectedArea(), totalSuitableLandCover * ModelConfig.MIN_NATURAL_RATE); - // if desired protected is more than suitable then protect all suitable - if (desiredProtectedArea + getUnavailableArea() > totalSuitableLandCover) { - LogWriter.printlnWarning("Desired protected area exceeds total suitable area"); - } - return Math.min(totalSuitableLandCover, desiredProtectedArea + getUnavailableArea()); - } - - /** averages protected areas across land cover types */ - private void updateUnprotectedAreas() { - double desiredProtected = getProtectedandUnavailable(); - double totalNatural = getTotalNatural(); - double unprotectedNat = Math.max(0, totalNatural - desiredProtected); // if desiredProtected more than total Natural then protect all - - double unprotectedFract = (totalNatural > 0) ? unprotectedNat/totalNatural : 0; - - for (LandCoverType landType : LandCoverType.values()) { - if (landType.isProtectable()) { - unprotectedAreas.put(landType, getLandCoverArea(landType)*unprotectedFract); - } - else unprotectedAreas.put(landType, getLandCoverArea(landType)); - } - } - - public void updateSuitableArea(int year) { - updateUnprotectedAreas(); //update unprotected areas - double currentAgri = getLandCoverArea(LandCoverType.PASTURE) + getLandCoverArea(LandCoverType.CROPLAND); - double totalNatural = getTotalNatural(); - double natAvailForAgri = totalNatural - getProtectedandUnavailable(); // could be negative, i.e. excess agricultural area already used - //this forces protected areas, min_natural_rate and slope constraints to be obeyed - if (ModelConfig.FORCE_PROTECTED_AREAS && natAvailForAgri < 0 && year >= ModelConfig.FORCE_PROTECTED_AREAS_START_YEAR) { - double proportion = 1.0; - - if (year < ModelConfig.FORCE_PROTECTED_AREAS_END_YEAR) - proportion = 1.0 / (ModelConfig.FORCE_PROTECTED_AREAS_END_YEAR - year); - - suitableArea = Math.max(0, currentAgri + natAvailForAgri * proportion); // netNatAvailForAgri is negative, but suitable area < 0 is not sensible (seems to happen with high barren areas) - } - else //not honouring protected areas if agriculture > natural that should be protected - suitableArea = currentAgri + getTotalUnprotectedNatural(); - } - public CropToDouble getCropChanges(LandUseItem prevAreaAggItem) { CropToDouble changes = new CropToDouble(); @@ -531,14 +459,16 @@ public class LandUseItem implements InterpolatingRasterItem<LandUseItem>, Serial return changes; } - public void doForestRotation(Timestep timestep, double rotationAge) { - ArrayList<LandUseTile> tiles = landUseTiles.get(LandCoverType.TIMBER_FOREST); - if (tiles == null) - return; - for (LandUseTile t : tiles) { - if (t.getAge(timestep) < rotationAge) - continue; - t.resetAge(timestep); // Set stand age to 0 + public void doForestRotation(int rotationAge) { + LandCoverTile tiles = landCoverAreas.get(LandCoverType.TIMBER_FOREST); + for (int a=rotationAge; a<=tiles.getMaxAgeBin(); a++) { + tiles.resetAreaForAge(a); + } + } + + public void incrementTilesAge() { + for (LandCoverTile tile : landCoverAreas.values()) { + tile.incrementAge(); } } @@ -549,10 +479,10 @@ public class LandUseItem implements InterpolatingRasterItem<LandUseItem>, Serial @Override public void interpolateAll(LandUseItem fromItem, LandUseItem toItem, double factor) { cropFractions = new HashMap<CropType, Double>(); - landCoverAreas = new HashMap<LandCoverType, Double>(); + landCoverAreas = new HashMap<LandCoverType, LandCoverTile>(); - Double fromCropCover = fromItem.landCoverAreas.get(LandCoverType.CROPLAND); - Double toCropCover = toItem.landCoverAreas.get(LandCoverType.CROPLAND); + Double fromCropCover = fromItem.landCoverAreas.get(LandCoverType.CROPLAND).getTotalArea(); + Double toCropCover = toItem.landCoverAreas.get(LandCoverType.CROPLAND).getTotalArea(); if (!isZeroOrNull(fromCropCover) && isZeroOrNull(toCropCover)) { // if start with crop but end with none, take starting crop fractions cropFractions.putAll(fromItem.cropFractions); @@ -583,10 +513,19 @@ public class LandUseItem implements InterpolatingRasterItem<LandUseItem>, Serial } for (LandCoverType landCover : LandCoverType.values()) { - Double from = fromItem.landCoverAreas.get(landCover); - Double to = toItem.landCoverAreas.get(landCover); + Double from = fromItem.landCoverAreas.get(landCover).getTotalConvertibleArea(); + Double to = toItem.landCoverAreas.get(landCover).getTotalConvertibleArea(); + Double d = Interpolator.interpolate(from, to, factor); + LandCoverTile tile = landCoverAreas.computeIfAbsent(landCover, k -> new LandCoverTile()); + tile.addConvertibleArea(d); + } + + for (LandCoverType landCover : LandCoverType.values()) { + Double from = fromItem.landCoverAreas.get(landCover).getTotalProtectedArea(); + Double to = toItem.landCoverAreas.get(landCover).getTotalProtectedArea(); Double d = Interpolator.interpolate(from, to, factor); - landCoverAreas.put(landCover, d); + LandCoverTile tile = landCoverAreas.computeIfAbsent(landCover, k -> new LandCoverTile()); + tile.addProtectedArea(d); } } @@ -624,6 +563,12 @@ public class LandUseItem implements InterpolatingRasterItem<LandUseItem>, Serial @Override public String toString() { - return "LandUseItem: [landCoverAreas=" + landCoverAreas + ", protectedArea=" + protectedArea + ", unavailableArea=" + unavailableArea + "]"; + Map<LandCoverType, Double> convertibleAreas = new HashMap<LandCoverType, Double>(); + Map<LandCoverType, Double> protectedAreas = new HashMap<LandCoverType, Double>(); + for (LandCoverType lcType : LandCoverType.values()) { + convertibleAreas.put(lcType, landCoverAreas.get(lcType).getTotalConvertibleArea()); + protectedAreas.put(lcType, landCoverAreas.get(lcType).getTotalProtectedArea()); + } + return "LandUseItem: [landCoverAreas=" + convertibleAreas + ", protectedArea=" + protectedAreas + "]"; } } \ No newline at end of file diff --git a/src/ac/ed/lurg/landuse/LandUseReader.java b/src/ac/ed/lurg/landuse/LandUseReader.java index a857eff3cedca22ca9e2d8027a0002586a9da0c6..bc8963300ff9d93b8d42c0c40ffd99df0566d548 100644 --- a/src/ac/ed/lurg/landuse/LandUseReader.java +++ b/src/ac/ed/lurg/landuse/LandUseReader.java @@ -21,10 +21,10 @@ public class LandUseReader extends AbstractTabularRasterReader<LandUseItem> { protected void setData(RasterKey key, LandUseItem luData, Map<String, Double> rowValues) { for (LandCoverType cover : LandCoverType.values()) { - luData.setLandCoverArea(cover, getValueForCol(rowValues, cover.getName())); + luData.setConvertibleLandCoverArea(cover, getValueForCol(rowValues, cover.getName())); } - luData.setProtectedArea(getValueForCol(rowValues, "protected")); + luData.setProtectedFraction(getValueForCol(rowValues, "protected") / luData.getTotalLandCoverArea()); for (CropType crop : CropType.getAllItems()) { double cropFract = getValueForCol(rowValues, crop.getGamsName() + "_A"); diff --git a/src/ac/ed/lurg/landuse/LandUseTile.java b/src/ac/ed/lurg/landuse/LandUseTile.java deleted file mode 100644 index 7af6fb1316a80fb2b5d6a646f9171a9fff3c1cb9..0000000000000000000000000000000000000000 --- a/src/ac/ed/lurg/landuse/LandUseTile.java +++ /dev/null @@ -1,64 +0,0 @@ -package ac.ed.lurg.landuse; - -import java.io.Serializable; - -import ac.ed.lurg.ModelConfig; -import ac.ed.lurg.Timestep; -import ac.ed.lurg.types.LandCoverType; -import ac.ed.lurg.utils.LogWriter; -import ac.sac.raster.InterpolatingRasterItem; - -public class LandUseTile implements Serializable, InterpolatingRasterItem<LandUseTile> { - private static final long serialVersionUID = 3208516181615267472L; - - private LandCoverType lcType; - private double area; // Mha - private int yearEstablished; - private boolean isProtected; - - public LandUseTile(LandCoverType lcType, double area, int yearEstablished, boolean isProtected) { - this.lcType = lcType; - this.area = area; - this.yearEstablished = yearEstablished; - this.isProtected = isProtected; - } - - public LandCoverType getLcType() { - return lcType; - } - - public double getArea() { - return area; - } - - public int getYearEstablished() { - return yearEstablished; - } - - public int getAge(Timestep timestep) { - return timestep.getYear() - yearEstablished; - } - - public boolean isProtected() { - return isProtected; - } - - public void resetAge(Timestep timestep) { - yearEstablished = timestep.getYear(); - } - - public void removeArea(double a) { - if (a > area) { - LogWriter.printlnError("LandUseTile.removeArea: attempting to remove too much area: " + a + ", available: " + area); - area -= Math.min(area, a); - } else { - area -= a; - } - } - - @Override - public void interpolateAll(LandUseTile fromItem, LandUseTile toItem, double factor) { - // TODO Auto-generated method stub - - } -} diff --git a/src/ac/ed/lurg/landuse/ProtectedAreasReader.java b/src/ac/ed/lurg/landuse/ProtectedAreasReader.java index dcf8ca6a17a83adf660fa8be887179d8621b0ac4..5a8b1e4d88d1dd4154c19c7c31ebe8d09ba09188 100644 --- a/src/ac/ed/lurg/landuse/ProtectedAreasReader.java +++ b/src/ac/ed/lurg/landuse/ProtectedAreasReader.java @@ -13,7 +13,7 @@ public class ProtectedAreasReader extends AbstractRasterReader<LandUseItem> { public void setData(LandUseItem lcData, String token) { if (!"nan".equals(token)) { double protFrac = Double.parseDouble(token); - lcData.setProtectedFract(protFrac); + lcData.setProtectedFraction(protFrac); } } } \ No newline at end of file diff --git a/src/ac/ed/lurg/output/LandUseOutputer.java b/src/ac/ed/lurg/output/LandUseOutputer.java index 3d341eb274927d963bacd1caad52d062f37a78ba..82eed7ea30d5d5841e87e1ca8bc37a94529a406e 100644 --- a/src/ac/ed/lurg/output/LandUseOutputer.java +++ b/src/ac/ed/lurg/output/LandUseOutputer.java @@ -58,8 +58,8 @@ public class LandUseOutputer extends AbstractLandUseOutputer { sbData.append(String.format(" %.8f", item.getTotalLandCoverArea())); sbData.append(String.format(" %.8f", item.getSuitableArea())); - sbData.append(String.format(" %.8f", item.getProtectedArea())); - sbData.append(String.format(" %.8f", item.getProtectedArea()/item.getTotalLandCoverArea())); + sbData.append(String.format(" %.8f", item.getTotalProtectedArea())); + sbData.append(String.format(" %.8f", item.getTotalProtectedArea()/item.getTotalLandCoverArea())); for (LandCoverType cover : LandCoverType.values()) { sbData.append(String.format(" %.8f", item.getLandCoverArea(cover))); diff --git a/src/ac/ed/lurg/types/LandCoverType.java b/src/ac/ed/lurg/types/LandCoverType.java index 311704e9c930c08996d607a4ef64660201fb5f87..7398c8a2c2f658ed7f264b70ea1c57f434cf00a0 100644 --- a/src/ac/ed/lurg/types/LandCoverType.java +++ b/src/ac/ed/lurg/types/LandCoverType.java @@ -39,6 +39,10 @@ public enum LandCoverType { return isProtectable; } + public boolean isConvertible() { + return isConvertible; + } + public boolean isNatural() { return isNatural; }