/farmR/src/java/src/jfm/model/CroppingComponent.java

https://code.google.com/p/javawfm/ · Java · 598 lines · 443 code · 45 blank · 110 comment · 81 complexity · 04b16a49dd8091072b55130e503ef162 MD5 · raw file

  1. package jfm.model;
  2. import java.text.DecimalFormat;
  3. import java.util.*;
  4. import jfm.lp.*;
  5. import jfm.model.Crop.CropCopy;
  6. import jfm.model.Types.*;
  7. /** Contains all crops and constructs matrix variables and constraints relating to crop and operation areas.
  8. * This is the key model component class for the model. It contains all crop objects, definitions for
  9. * all disease and rotation types, the overall area of cropping (farm area) and the limit
  10. * constraints which may be controlled by the user. Exposes its underlying crops via the
  11. * \c getCrop function to allow them to be externally manipulated.
  12. *
  13. * @author Ira Cooke */
  14. public final class CroppingComponent extends ModelComponent {
  15. // ---- Cropping Data ---- //
  16. private final Map<DiseaseType,Disease> diseases=new HashMap<DiseaseType,Disease>();
  17. // private final Map<CropType,Rotation> rotations=new HashMap<CropType,Rotation>();
  18. private final List<Limit> limits= new ArrayList<Limit>(); // Limits on Areas or other quantities for crops
  19. private final Map<CropType,Crop> crops=new HashMap<CropType,Crop>(); // List of base crops
  20. private final Set<CropYear> cropYearsSet=new LinkedHashSet<CropYear>();// Handy aliase for each of the crop years across all types
  21. private static String constraintTag="Cropping";
  22. // private double cropArea; // Total crop area avail on this farm
  23. /** Gets a specified disease object.
  24. * @return The specified disease
  25. * @param type The desired disease type
  26. * */
  27. public Disease getDisease(DiseaseType type){
  28. if ( diseases.containsKey(type)){
  29. return diseases.get(type);
  30. } else {
  31. throw new Error("Attempt to get undefined disease of type "+type);
  32. }
  33. }
  34. /** Attempt to replace a disease type with a new Disease object.
  35. * @param type The disease type to be replaced
  36. * @param newDisease The new disease object to be associated with type
  37. * @return true if the operation was successful
  38. * */
  39. public boolean replaceDisease(DiseaseType type,Disease newDisease){
  40. if ( diseases.containsKey(type)){
  41. diseases.put(type, newDisease);
  42. return true;
  43. } else {
  44. return false;
  45. }
  46. }
  47. /** Returns the crop copy corresponding to a particular year of a particular CropType.
  48. * If the requested crop type is not present an Error is thrown.
  49. * @param baseType The base type of the requested crop. */
  50. public Crop getCrop(CropType baseType){
  51. if ( !crops.containsKey(baseType)){
  52. throw new Error("Request for undefined crop type "+baseType);
  53. }
  54. return crops.get(baseType);
  55. }
  56. public void setFormulaVariables(){
  57. // System.out.println("Setting fvars for cropping "+crops.values().size());
  58. for ( Crop cp: crops.values()){
  59. // System.out.println("Setting vars for "+cp.type);
  60. cp.setFormulaVariables(getParent().location());
  61. }
  62. }
  63. /** @return A map from crop types to copies of the crops in this object.
  64. * Returned crops are deregistered from the parent object */
  65. public Map<CropType,Crop> copyCrops(){
  66. Map<CropType,Crop> cps = new LinkedHashMap<CropType,Crop>();
  67. for ( Crop cp: crops.values()){
  68. Crop cpcpy=cp.copy();
  69. cpcpy.deRegisterCopies();
  70. cps.put(cp.type, cpcpy);
  71. }
  72. return cps;
  73. }
  74. /** @return An unmodifiable view of all the crops . */
  75. public Map<CropType,Crop> getCrops(){
  76. Map<CropType,Crop> cps = Collections.unmodifiableMap(crops);
  77. return cps;
  78. }
  79. /** Returns an immutable view of the set of all base CropTypes for this Cropping object. */
  80. public Set<CropType> baseCropTypes(){
  81. return Collections.unmodifiableSet(crops.keySet());
  82. }
  83. /** Returns an immutable view of the set of all CropYears for this Cropping object. */
  84. public Set<CropYear> cropYears(){
  85. return Collections.unmodifiableSet((cropYearsSet));
  86. }
  87. // public List<Limit> getLimits(){return Collections.unmodifiableList(limits);};
  88. public void clearCrops(){
  89. crops.clear();
  90. cropYearsSet.clear();
  91. requireMatrixRebuild();
  92. }
  93. /** Clear all the Limits. */
  94. public void clearLimits(){
  95. limits.clear();
  96. requireMatrixRebuild();
  97. }
  98. /** Add a Limit to this Cropping object.
  99. * This function does not copy its argument when adding
  100. * @param lim A new limit to be added */
  101. public void addLimit(Limit lim){
  102. limits.add(lim);
  103. requireMatrixRebuild();
  104. }
  105. // CONSTRUCTOR //
  106. public CroppingComponent(){
  107. super(ModelComponent.MCType.CROPPING);
  108. requireObjective(ObjectiveType.PROFIT);
  109. addConstraintBuilder(new OperationAreaConstraint());
  110. addConstraintBuilder(new OperationSequencingConstraint());
  111. addConstraintBuilder(new NonSequentialOperationConstraint());
  112. addConstraintBuilder(new DiseaseConstraint());
  113. addConstraintBuilder(new OperationMinAreaConstraint());
  114. addConstraintBuilder(new AreaLimitConstraint());
  115. }
  116. /** Add a new type of Crop and its associated year copies.
  117. * This function does not copy its argument when adding.
  118. * Throws an Error if the type of crop to be added is already present.
  119. * @param newCrop A Crop object of the type to be added */
  120. public void addCrop(Crop newCrop){
  121. CropType type=newCrop.type;
  122. if ( crops.containsKey(type)){
  123. throw new Error("Can't add crop of type "+type+" because there is already a crop with that type");
  124. }
  125. for ( int i=0;i<newCrop.getYearCopies().size();i++){
  126. cropYearsSet.add(new CropYear(type,i));
  127. }
  128. crops.put(type,newCrop);
  129. // addRotation(newCrop.rotation());
  130. requireMatrixRebuild();
  131. }
  132. /** Add a new Disease corresponding to a particular base DiseaseType.
  133. * This function does not copy its argument when adding.
  134. * Throws an Error is the base type of the new disease is already present.
  135. * @param newDisease A Disease object to be added */
  136. public void addDisease(Disease newDisease){
  137. DiseaseType baseType=newDisease.base;
  138. if ( diseases.containsKey(baseType)){
  139. throw new Error("Can't add disease of type "+baseType+" because a disease with that type already exists ");
  140. }
  141. diseases.put(baseType, newDisease);
  142. requireMatrixRebuild();
  143. }
  144. /** Add a new Rotation penalty specification corresponding to a DiseaseType in the crops being rotated to.
  145. * This function does not copy its argument when adding.
  146. * @param newRotation The Rotation object specifying the penalties */
  147. /* private void addRotation(Rotation newRotation){
  148. CropType toType = newRotation.to;
  149. if ( rotations.containsKey(toType)){
  150. throw new Error("Can't add rotation of disease type "+toType+" because a rotation penalty for that disease already exists ");
  151. }
  152. // System.out.println("Adding: "+toType+": "+newRotation.getPenalty(toType)[0]+" "+newRotation.getPenalty(toType)[1]);
  153. rotations.put(toType, newRotation);
  154. requireMatrixRebuild();
  155. }*/
  156. /** Create a working copy of all the objects contained within this Cropping object.
  157. * Performs a deep copy on all mutable objects such as crops, limits and rotations, and a shallow copy
  158. * of immutable objects such as diseases and rotations. Changes in the copied object will not
  159. * be reflected in the original.
  160. * @return A working copy of the current object */
  161. public CroppingComponent copy(){
  162. CroppingComponent cpcpy = new CroppingComponent();
  163. for ( Crop cb:crops.values()){
  164. cpcpy.addCrop(cb.copy());
  165. }
  166. for(Disease dp:diseases.values()){
  167. cpcpy.addDisease(dp);
  168. }
  169. /*
  170. for(Rotation rp:rotations.values()){
  171. cpcpy.addRotation(rp.copy());
  172. }*/
  173. for(Limit lm:limits){
  174. cpcpy.addLimit(lm.copy());
  175. }
  176. return cpcpy;
  177. }
  178. /** Get a list of all the CropCopy objects which have a non-zero solved area */
  179. List<CropCopy> cropsUsed(){
  180. List<CropCopy> thelist=new ArrayList<CropCopy>();
  181. for ( CropYear cy:cropYearsSet){
  182. CropCopy cp=crops.get(cy.base).getCopy(cy.copyYear);
  183. if ( cp.getSolvedArea() > 0 ){
  184. // System.out.println(cp.name()+cp.getSolvedArea());
  185. thelist.add(cp);
  186. }
  187. }
  188. return thelist;
  189. }
  190. double[] getRotationPenalty(CropType to,DiseaseType from){
  191. if ( crops.containsKey(to)){
  192. Rotation torot=crops.get(to).rotation();
  193. return torot.getPenalty(from);
  194. } else {
  195. double[] nocosts={0,0};
  196. return nocosts;
  197. }
  198. }
  199. Set<CropType> cropsWithDisease(DiseaseType dis){
  200. Set<CropType> cps=new HashSet<CropType>();
  201. if ( dis==DiseaseType.NONE){
  202. return cps; // If disease is None then we just return the empty set
  203. }
  204. for(CropType ctype: crops.keySet()){
  205. if ( crops.get(ctype).diseaseType==dis){
  206. cps.add(ctype);
  207. }
  208. }
  209. return cps;
  210. }
  211. /** Checks to make sure that all the required diseases and rotations have been defined
  212. * for the crops present */
  213. public boolean diseasesAndRotationsComplete(){
  214. for (Crop cp:crops.values()){
  215. if ( !diseases.containsKey(cp.diseaseType) && cp.diseaseType!=DiseaseType.NONE){
  216. return false;
  217. }
  218. }
  219. return true;
  220. }
  221. protected void initializeStructure(){
  222. Location location=getParent().location();
  223. for(CropType ct:crops.keySet()){
  224. List<CropCopy> clist=crops.get(ct).getYearCopies();
  225. for(int i=0;i<clist.size();i++){
  226. CropCopy cp=clist.get(i);
  227. cp.registerParent(this);
  228. MatrixVariable newVariable=new MatrixVariable(cp.grossMargin(),
  229. 0.0,1.0,
  230. LPX.LPX_LO,LPX.LPX_CV,matrix.numCols(),ObjectiveType.PROFIT);
  231. newVariable.setTag(ct.xmlname+"_"+cp.copyYear());
  232. matrix.addVariable(newVariable); // The gross margin variable for this crop
  233. cp.registerVariable(newVariable,0);
  234. // Now the operation variables for this crop
  235. for(Operation op:cp.operations()){
  236. op.registerParent(this);
  237. List<Integer> pallow = new ArrayList<Integer>(op.unfoldedAllowedSet());
  238. Collections.sort(pallow); // We need to ensure that columns for each set of periods
  239. for(Integer p:pallow){
  240. newVariable=new MatrixVariable(-op.cost(p,cp,location,getParent().fuelPrice()),0,0,
  241. LPX.LPX_LO,LPX.LPX_CV,matrix.numCols(),ObjectiveType.PROFIT);
  242. matrix.addVariable(newVariable);
  243. newVariable.setTag(ct.xmlname+"_"+cp.copyYear()+"_"+op.type.shortName+"_"+p);
  244. op.registerVariable(newVariable,p);
  245. }
  246. }
  247. }
  248. }
  249. }
  250. protected void updateStructure(){
  251. for(Crop cp:crops.values()){
  252. for ( CropCopy cy:cp.getYearCopies()){
  253. cy.updateStructure(this);
  254. }
  255. }
  256. structureUpdateDone();
  257. }
  258. private final void buildLimitAreaConstraint(Limit lim){
  259. int row=matrix.numRows();
  260. MatrixRow rowpointer =new MatrixRow(lim.min,lim.max,lim.type,
  261. row,constraintTag,"AreaLimit");
  262. matrix.addRow(rowpointer);
  263. row++;
  264. Set<CropType> limCrops=new HashSet<CropType>(lim.getCropSubjects());
  265. Set<CropType> allCrops=crops.keySet();
  266. limCrops.retainAll(allCrops); // Ensures only crops that actually exist get this constraint applied;
  267. if ( limCrops.size() != lim.getCropSubjects().size()){
  268. throw new Error("One or more limit crops in the list \n"+lim.getCropSubjects()+
  269. "\n were not specified in the cropping model \n"+allCrops);
  270. }
  271. for(CropType ct:limCrops){
  272. List<CropCopy> cplist=crops.get(ct).getYearCopies();
  273. for( CropCopy cp:cplist){
  274. rowpointer.addElement(new MatrixElement(cp.getDependentColumn(0),1));
  275. }
  276. }
  277. }
  278. /** Constrain the area of each crop to equal the area of the first operation for that crop.
  279. *
  280. * @author Ira Cooke
  281. *
  282. * */
  283. public final class OperationAreaConstraint extends ConstraintBuilder {
  284. public OperationAreaConstraint(){
  285. super(ConstraintBuilder.CBType.OPAREA,ModelComponent.MCType.CROPPING);
  286. }
  287. protected void build(){
  288. int row=matrix.numRows();
  289. for(CropType ct:crops.keySet()){
  290. for(CropCopy cp:crops.get(ct).getYearCopies()){
  291. MatrixRow rowpointer =new MatrixRow(0,0,LPX.LPX_FX,
  292. row,constraintTag,type().tag+"_"+ct);
  293. matrix.addRow(rowpointer);
  294. row++;
  295. rowpointer.addElement(new MatrixElement(cp.getDependentColumn(0),-1));
  296. Operation op=cp.operations().get(0); // The first operation is the only one that is relevant here
  297. for(Integer p:op.unfoldedAllowedSet()){
  298. rowpointer.addElement(new MatrixElement(op.getDependentColumn(p),1));
  299. // }
  300. }
  301. }
  302. }
  303. }
  304. }
  305. /** Constrain the total area of each operation (regardless of sequence) to equal the crop area.
  306. *
  307. * @author Ira Cooke
  308. *
  309. * */
  310. public final class NonSequentialOperationConstraint extends ConstraintBuilder{
  311. public NonSequentialOperationConstraint(){
  312. super(ConstraintBuilder.CBType.NONSEQOP,ModelComponent.MCType.CROPPING);
  313. }
  314. protected void build(){
  315. int row=matrix.numRows();
  316. for ( CropType ct:crops.keySet()){ // Loop Crops
  317. for(CropCopy cp:crops.get(ct).getYearCopies()){
  318. List<Operation> cropOps=cp.operations();
  319. int nops = cropOps.size();
  320. for ( int o=1;o<nops;o++){ // Loop operations 1 to max
  321. Operation op = cropOps.get(o);
  322. MatrixRow rowpointer =new MatrixRow(0,0,LPX.LPX_FX,
  323. row,constraintTag,type().tag+"_"+ct+"_"+op.type);
  324. matrix.addRow(rowpointer);
  325. rowpointer.addElement(new MatrixElement(cp.getDependentColumn(0),-1)); // Make a constraint for each crop op combo
  326. row++;
  327. for ( Integer p:op.unfoldedAllowedSet()){
  328. rowpointer.addElement(new MatrixElement(op.getDependentColumn(p),1));
  329. }
  330. }
  331. }
  332. }
  333. }
  334. }
  335. /** Constrain the operational areas to conform to sequencing requirements. The sum of operation areas in a given period cannot exceed
  336. * the sum of areas of the preceeding operation up to that period (or up to an earlier period if a gap is enforced) */
  337. public final class OperationSequencingConstraint extends ConstraintBuilder {
  338. public OperationSequencingConstraint(){
  339. super(ConstraintBuilder.CBType.OPSEQ,ModelComponent.MCType.CROPPING);
  340. }
  341. protected void build(){
  342. int row=matrix.numRows();
  343. for(CropType ct:crops.keySet()) { // Loop Crops
  344. List<CropCopy> clist=crops.get(ct).getYearCopies();
  345. for(int i=0;i<clist.size();i++){ // Loop crop copies
  346. CropCopy cp = clist.get(i);
  347. List<Operation> cpOps=cp.operations();
  348. int nops = cpOps.size();
  349. for ( int o=1;o<nops;o++){ // Loop operations 1 to max
  350. Operation op = cpOps.get(o);
  351. if ( op.isSequential()){
  352. Operation prevop=cpOps.get(o-1);
  353. /* Because we are considering operations within a
  354. single crop growth cycle we use the unfolded periods */
  355. Set<Integer> opallow=(Set<Integer>)op.unfoldedAllowedSet();
  356. Set<Integer> prevopallow=(Set<Integer>)prevop.unfoldedAllowedSet();
  357. Set<Integer> peitherallow=new HashSet<Integer>(opallow);
  358. Set<Integer> pbothallow = new HashSet<Integer>(opallow);
  359. peitherallow.addAll(prevopallow);
  360. pbothallow.retainAll(prevopallow);
  361. /* For the case where a gap spans the allowable operations we need to
  362. * reconstruct the set of periods to include those in the gap */
  363. Set<Integer> pbothallowPlusGap = new HashSet<Integer>(pbothallow);
  364. int popmin=Collections.min(opallow);
  365. for ( int g=1;g<=prevop.gap;g++){
  366. int pgap=popmin-g;
  367. if ( prevopallow.contains(pgap)){
  368. pbothallowPlusGap.add(pgap);
  369. }
  370. }
  371. // Make a check to see if the gap is feasible
  372. if ( Collections.min(prevopallow)+prevop.gap > Collections.max(opallow)){
  373. throw new Error("The sequence of operations "+prevop.type+" followed by "+
  374. op.type+" in crop "+cp.name()+" has a sequencing infeasibility \n ---> check dates and mingap requirements \n");
  375. }
  376. if ( pbothallowPlusGap.size()>0){ // The intersection is not null
  377. for ( Integer pboth:pbothallowPlusGap){
  378. MatrixRow rowpointer =new MatrixRow(0,0,LPX.LPX_LO,
  379. row,constraintTag,type().tag+"_"+ct+"_"+op.type+"_p"+pboth);
  380. matrix.addRow(rowpointer);
  381. row++;
  382. for ( int p=Collections.min(peitherallow);p<=pboth;p++){
  383. if ( opallow.contains(p)){
  384. rowpointer.addElement(new MatrixElement(op.getDependentColumn(p),-1)); // Make a constraint for each op,period,crop combo
  385. }
  386. // if ( prevop.gap > 0 ){
  387. // System.out.println(pbothallowPlusGap);
  388. // throw new Error("Stop "+prevop.gap);
  389. // }
  390. if ( prevopallow.contains(p) && (p+prevop.gap)<=pboth){
  391. rowpointer.addElement(new MatrixElement(prevop.getDependentColumn(p),1));
  392. }
  393. }
  394. }
  395. }
  396. }
  397. }
  398. }
  399. }
  400. }
  401. }
  402. /** If specified, the area of a particular operation is constrained to be at least
  403. * a certain minimum percentage of the total crop area */
  404. public final class OperationMinAreaConstraint extends ConstraintBuilder {
  405. public OperationMinAreaConstraint(){
  406. super(ConstraintBuilder.CBType.OPMINAREA,ModelComponent.MCType.CROPPING);
  407. }
  408. protected void build(){
  409. int row=matrix.numRows();
  410. for(CropType ct:crops.keySet()) { // Loop Crops
  411. List<CropCopy> clist=crops.get(ct).getYearCopies();
  412. for(int i=0;i<clist.size();i++){ // Loop crop copies
  413. CropCopy cp = clist.get(i);
  414. List<Operation> cpOps=cp.operations();
  415. int nops = cpOps.size();
  416. for ( int o=0;o<nops;o++){ // Loop operations 1 to max
  417. Operation op = cpOps.get(o);
  418. if ( op.hasMinAreas()){
  419. Set<Integer> pallow=op.unfoldedAllowedSet();
  420. for ( Integer p:pallow){
  421. if (op.minArea(p)>0){
  422. MatrixRow rowpointer =new MatrixRow(0,0,LPX.LPX_LO,
  423. row,constraintTag,type().tag+"_"+ct+"_"+op.type+"_p"+p);
  424. matrix.addRow(rowpointer);
  425. row++;
  426. rowpointer.addElement(new MatrixElement(op.getDependentColumn(p),1));
  427. rowpointer.addElement(new MatrixElement(cp.getDependentColumn(0),-op.minArea(p)));
  428. }
  429. }
  430. }
  431. }
  432. }
  433. }
  434. }
  435. }
  436. /** Constrain the area of crops with a disease to be no greater than */
  437. public final class DiseaseConstraint extends ConstraintBuilder{
  438. public DiseaseConstraint(){
  439. super(ConstraintBuilder.CBType.DISEASE,ModelComponent.MCType.CROPPING);
  440. }
  441. protected void build(){
  442. int row=matrix.numRows();
  443. for ( DiseaseType dt:diseases.keySet()){
  444. Disease dis=diseases.get(dt);
  445. Disease assocdis=diseases.get(dis.associated);
  446. MatrixRow rowpointer =new MatrixRow(0,0,LPX.LPX_LO,
  447. row,constraintTag,"disease_"+dt);
  448. matrix.addRow(rowpointer);
  449. row++;
  450. Set<CropType> withdisease=cropsWithDisease(dt);
  451. Set<CropType> withassociated=cropsWithDisease(dis.associated);
  452. for(CropType croptype:crops.keySet()){
  453. CropCopy cp=crops.get(croptype).getCopy(0);
  454. if ( !withdisease.contains(croptype) && !withassociated.contains(croptype)){
  455. rowpointer.addElement(new MatrixElement(cp.getDependentColumn(0),1));
  456. } else {
  457. if ( withdisease.contains(croptype) && !withassociated.contains(croptype)){
  458. rowpointer.addElement(new MatrixElement(cp.getDependentColumn(0),-dis.minBreak));
  459. } else if ( withassociated.contains(croptype) && !withdisease.contains(croptype)){
  460. rowpointer.addElement(new MatrixElement(cp.getDependentColumn(0),-assocdis.minBreak));
  461. } else {
  462. rowpointer.addElement(new MatrixElement(cp.getDependentColumn(0),-Math.min(assocdis.minBreak,dis.minBreak)));
  463. }
  464. }
  465. }
  466. }
  467. }
  468. }
  469. /** Constrain the areas of specific crops.
  470. * @author Ira Cooke
  471. *
  472. * */
  473. public final class AreaLimitConstraint extends ConstraintBuilder {
  474. public AreaLimitConstraint(){
  475. super(ConstraintBuilder.CBType.AREALIMIT,ModelComponent.MCType.CROPPING);
  476. }
  477. protected void build(){
  478. for(Limit lim:limits){
  479. switch(lim.limitType){
  480. case AREA:
  481. buildLimitAreaConstraint(lim);
  482. break;
  483. default:
  484. throw new Error("No constraint defined for limit type"+lim.limitType);
  485. }
  486. }
  487. }
  488. }
  489. public String name(){return "cropping";};
  490. // ---- Reporting --- //
  491. public String toString(){
  492. StringBuffer outstring = new StringBuffer();
  493. outstring.append("--- Cropping --- \n");
  494. outstring.append("Total Area ");
  495. outstring.append("\n");
  496. for(Crop cp:crops.values()){
  497. outstring.append(cp);
  498. }
  499. outstring.append("Diseases \n");
  500. for ( Disease ds:diseases.values()){
  501. outstring.append(ds);
  502. }/*
  503. outstring.append("Rotations \n");
  504. for ( Rotation rt:rotations.values()){
  505. outstring.append(rt);
  506. }*/
  507. outstring.append("Limits \n");
  508. for ( Limit lt:limits){
  509. outstring.append(lt);
  510. }
  511. return outstring.toString();
  512. }
  513. /** Pretty print the solved areas of each of the crops.
  514. * Optionally print the detailed breakdown of operations for each crop in
  515. * each period
  516. *
  517. * @param model The parent model holding this cropping object
  518. * @param detailed Flag whether or not to print detailed operations data */
  519. public String printSolution(Farm model,boolean detailed){
  520. StringBuffer outstring = new StringBuffer();
  521. DecimalFormat f1 = new DecimalFormat("#0.00");
  522. outstring.append("--- Cropping --- \n");
  523. outstring.append("Total Area ");
  524. outstring.append("\n");
  525. for ( CropYear cy:cropYearsSet){
  526. CropCopy cp = crops.get(cy.base).getCopy(cy.copyYear);
  527. if ( cp.solvedArea()>0){
  528. outstring.append(cp.name()+": "+f1.format(cp.solvedArea())+"\n");
  529. }
  530. }
  531. if ( detailed ){
  532. outstring.append("\n");
  533. for ( CropYear cy:cropYearsSet){
  534. CropCopy cp = crops.get(cy.base).getCopy(cy.copyYear);
  535. if ( cp.solvedArea()>0){
  536. outstring.append(cp.printSolution(' '));
  537. }
  538. }
  539. }
  540. return outstring.toString();
  541. }
  542. public String printCSVWorkPlan(){
  543. StringBuffer outstring = new StringBuffer();
  544. for ( CropYear cy:cropYearsSet){
  545. CropCopy cp = crops.get(cy.base).getCopy(cy.copyYear);
  546. outstring.append(cp.name()+"\n");
  547. if ( cp.solvedArea()>0){
  548. outstring.append(cp.printSolution(','));
  549. }
  550. }
  551. return outstring.toString();
  552. }
  553. }