Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor ResourcesAI.py #580

Merged
merged 19 commits into from May 12, 2016
Merged

Conversation

LGM-Doyle
Copy link
Contributor

This PR is a major refactor of ResourcesAI.py.

I was trying to determine why the AI appears to be precessing around a threshold when toggling between research and industry focus every third turn. It turns out adj_round 3 intentionally trades 3 industry points for 1 research point when targeting low a research/industry ratio.

The PR fixes one small bug preventing more than one high industry planet from being prevented from switching to research and otherwise doesn't intentionally change any logic.

The refactor is mostly of three parts.

  1. Rewrote set_planet_resource_foci into a function that calls 5 sub-functions: use_planet_growth_specials, use_planet_production_and_research_specials, set_planet_protection_foci, set_planet_happiness_foci (,a placeholder) and set_planet_industry_and_research_foci. They all do the obvious duty in the same manner as before. They do not access any non-local variables other than fo, the freeorion global.
  2. Replaced the file scope variables new_targets, current_focus, current_output and planet_map with a focus_manager. The focus_manager includes planet info for each planet encompassing the previous variables. focus_manager manages all updating or changes of state of the focus setting. Most significantly focus_manager uses bake_future_focus to change a planet's status from raw to baked as its focus is set. Previously, the behavior was implicit and if a planet was set, but not removed from the list it would be overwritten later. All setting happens in this one function as opposed to throughout the file.
  3. Moved all reporting into a Reporter object. The reporter.capture_section_info is called after each stage of the set_planet_resource_foci to capture the information to make table sections: Unfocusable, Specials, Protection, Happiness and Typical.

I think the refactored code is clearer to read.
The code is more local in scope and hence easier to modify in future.

Create separate functions for growth specials, research and industry
specials, protection focus, happiness focus (empty placeholder) and
industry and research foci.

Thread variables through new functions to clarify which function
changes which variable.
Move adj_round 1, calculate minimum possible R/I ratio into its own loop.
The code to exclude research focus for high industry planets was indented
to the depth of the debugging header and only ran when the header was
printed.
…e_with_underscore

Renamed newTarget, currentFocus, currentOutput and planetMap to
new_targets, current_focus, current_output and planet_map

Removed treatment of newTargets as a pseudo global from AIstate
PlanetFocusManager replaces the file scope variables new_targets,
current_focus, current_output and planet_map.  It also replaces
new_foci.

focus_manager is an instance and is limited in scope to the
set_planet_resource_foci function.

focus_manager is threaded through the functions that previously accessed
the file scope variables.
set_future_focus sets the focus if possible and removes the planet from
the list of planets to be considered for further focus changes.
Created PlanetFocusInfo object to contain all information about a focus
change and transfer these objects from raw (unset) to baked (set) state.

This makes explicit the implicit assumption that planet_ids only
referenced planets whose focus had not been decided.

Added to PlanetFocusManager three tables all_planet_info,
raw_planet_info and baked_planet_info.  As the algorithm proceeds planet
info is moved from the raw table to the baked table using
bake_future_focus(pid, focus).

Added the PlanetFocusInfo object to encapsulate all the information
needed to decide how to set the focus.

Removed the PlanetFocusManager members new_targets, current_focus,
current_output, new_foci whose information is now contained in the
individual planet infos.

bake_future_focus(pid,focus) is the function that transfers planet info
from the raw_ list to the baked_ list.
Moved most reporting into a Reporter class

reporter.capture_section_info() captures which planets' focus was decided
after each of the following steps:
"Unfocusable", "Specials", "Protection", "Happiness" and "Typical"

Sections of the table are only printed if they exist.
Ran pylint and fixed all 'Wrong continued indentation before block'
errors to conform to PEP8
In ResourcesAI.Reporter compute the cumulative industry and research
targets and min/max boundaries in the reporter instead of passing them
from elsewhere.
"""Set the focus and moves it from the raw list to the baked list of planets
Return success or failure"""
pinfo = self.raw_planet_info.get(pid)
success = (pinfo != None and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://www.python.org/dev/peps/pep-0008/#id45:
Comparisons to singletons like None should always be done with is or is not , never the equality operators.

PS. Just run code checker to fix all style issues at once.

LGM-Doyle added a commit to LGM-Doyle/freeorion that referenced this pull request Apr 3, 2016
@LGM-Doyle
Copy link
Contributor Author

Thanks for the review.

What code checker are you using?

I used pylint. I ignored variable naming violations, missing docstrings, too many branches and too many statements as there were already violations in the old code. It did not pick up the violations that you pointed out, use of None etc.

The last string formatting code was in the legacy code and was only moved. I didn't write it and wish to dodge the blame. I did fix it as requested.

@Cjkjvfnby
Copy link
Contributor

I use embedded checker in PyCharm (Community edition is free). It highlight warnings in code.

| The PR fixes one small bug preventing more than one high industry planet from being prevented from switching to research and otherwise doesn't intentionally change any logic.

Can you rephrase phrase in description because I don sure that I understand it properly

You can use insert code button at the title of edit area to wrap code names in description it will add them visual highlighting.

See also Styling with Markdown is supported link at the bottom of edit area, there is a lot of useful text formatting features. You can use real list instead of manually creating them. And make sublists inside them.

itarget = pinfo.planet.currentMeterValue(fo.meterType.targetIndustry)
rtarget = pinfo.planet.currentMeterValue(fo.meterType.targetResearch)
pinfo.possible_output[focus] = (itarget, rtarget)
return success
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PyCharm generates me next hint:

Inferred type: (self: PlanetFocusManager, pid: Any, focus: Any, update: bool) -> Union[bool, int]

Just wrap success to bool before return it.

@Cjkjvfnby
Copy link
Contributor

@Cjkjvfnby Cjkjvfnby added category:refactoring The Issue/PR describes or contains an improved implementation. component:AI The Issue/PR deals with the Python AI decision making code or affects it. labels Apr 3, 2016
@Cjkjvfnby Cjkjvfnby self-assigned this Apr 3, 2016
@Cjkjvfnby Cjkjvfnby added this to the Next release milestone Apr 3, 2016
universe.updateMeterEstimates(self.raw_planet_info.keys())
itarget = pinfo.planet.currentMeterValue(fo.meterType.targetIndustry)
rtarget = pinfo.planet.currentMeterValue(fo.meterType.targetResearch)
pinfo.possible_output[focus] = (itarget, rtarget)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure about the logic of the entire block here.
First, you remove pid out of raw_planet_info, then you update meter estimates but only for the raw_planet_info, thus not pid, yet try to update pinfo which is paired with pid?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pinfo object is transferred from the raw_planet_info to baked_planet_info. That same pinfo object is the one that is updated.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes... But pid is not included in the updateMeterEstimates call, is that intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct.
It was intentional, but then I moved the code to grab the meterValues for the current planet to after the point where pinfo was transferred.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calculate_planet_infos already calculates all planet info so we can remove pinfo.possible_output[focus] = (itarget, rtarget) form here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calculate_planet_infos() is called after all of the planet foci with global effects are set.

However, bake_future_focus() is called to set the planets with global effects (before calculate_planet_infos is called) and at that point they still need their pinfo.possible_output[focus] to be calculated.

The method avoids having to re-calculate all of the planets possible_outputs after every focus setting event.

1. Add note about lack of stability wrt pid ordering to replace
commented shuffle code.
2. Improved docstring for bake_future_focus, to clarify usage of update.
3. Fixed bake_future_focus and made it remove current planet from raw
planets after updating meters.
4. Made all docstrings use """ not '''.
5. More PEP8
self.current_output[IFocus] = planet.currentMeterValue(fo.meterType.industry)
self.current_output[RFocus] = planet.currentMeterValue(fo.meterType.research)
self.current_output = (planet.currentMeterValue(fo.meterType.industry)
, planet.currentMeterValue(fo.meterType.research))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave coma at previous line

1. Add space after # in comments
2. Move split line commas to end of previous line.
3. Clarify comment about target vs current population in enabling focus
@Dilvish-fo
Copy link
Member

Hi, LGM-Doyle, the refactoring looks good to me. Also, nice job catching the bug, indentation errors can be tricky enough that even after your initial note above I still had to look pretty close to spot the bug and fix. That might actually wind up being a pretty notable bug fix, I'm interested in seeing how that works out.

Also, in that section of code the entire use of the variable "do_research" is just a holdover from a previous way of tracking preset focii; in the current code (and your refactoring) all uses of do_research could simply be removed.

If you had responded to Cj's question about the bug description, I'm not seeing it. I would phrase it as "The PR fixes an indentation error which prevented more than one high production planet from bypassing Research Focus during the final assessment of the local research_potential : industry_potential ratio."

To confirm that the refactoring itself did not cause any unintended changes of focus assignments, did you do any save-game testing without the bugfix in place?

#TODO Could this be changed to current population instead of target population?
#It uses the target population to avoid situations where an unsustainable population is dying?
# It uses the target population versus current population to avoid situations
# where an unsustainable population is dying?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's my recollection that a while in the past, when a colony died down to an outpost it still retained its last-set focus, which could not be changed, hence a test like the one here (though I don't recall why it would look at target pop rather than current pop). Nowadays the focus is reset to nothing in that case, so I think this test would no longer be needed (or perhaps simply be changed to look for nonempty AvailableFoci).

@LGM-Doyle
Copy link
Contributor Author

Dilvish, thanks for the review.

I have removed do_research and can_change_focus().

I answered CJ's question above. It is one of the collapsed comments on my browser. Do you want me to change the commit message? I thought the change in the code was clear.

To check that I didn't break anything as I refactored I looked for automated tests. There only appears to be a testing build target in the CMakefiles for the parser.

So, here is how I tested.

Before I changed anything I ran 150 turns of a random but specific universe. I picked two empires and observed their progress: one that grew successfully (AI4) and one that did not (AI3). I kept their AI_N.log files as an example of a correctly working resource allocation. I ran the same universe with ResourcesAI completely disabled and took note of the results of complete failure.

Then I added noisy debugging print statements, since removed, to adj_round 2, 3 and 4 and the ratio loop just past their guard conditions. That is how I noticed that the header printing was masking further research assignments in the ratio portion of the algorithm.

After the refactor before I removed the noisy debugging statements AI3 and AI4 were still developed along similar lines and were toggling foci in a similar manner.

As the refactor was extensive, and there is no automated testing I can NOT say that I haven't introduced any new bugs.

@Cjkjvfnby
Copy link
Contributor

I still have question:

calculate_planet_infos already calculates all planet info so we can remove pinfo.possible_output[focus] = (itarget, rtarget) form here

Last time I try to automate test, I was stopped by some randomness that can-not be controlled by AI. I don't remember what exactly it was.

@LGM-Doyle
Copy link
Contributor Author

Cj, here are some of my quick thoughts/questions on testing the AI.

For testing the AI, I have only looked at ResourcesAI.py. It is definitely influenced by the globals, foAI, empire and universe. One way to make testing repeatable would be to have fake/mock foAI, empire and universe objects that just feed in expected test data and respond non-randomly to expected changes or fail the test.

I would be interested in adding testing as a developer tool to the python: adding a test_AI target to the CMake files etc.

I would not be interested in trying to write tests for all of the legacy code.

What is your preferred python testing framework?

Should I create an AI testing github issue even if it is an umbrella issue that is never checked off?

@Cjkjvfnby
Copy link
Contributor

@LGM-Doyle, you mean unittest for code, executed outside of the AI? This require to split AI to modules (which is nice).

For python I prefer pytest. It full of magic but do a lot of work for you. On windows machine I does not manage to make it work inside AI. (freeorion use own python for game proposes for win builds)

According to branch I have no comments left. Lets merge it.

@Cjkjvfnby
Copy link
Contributor

@Dilvish-fo, @Morlic-fo, any objections about merge it?

"""Assess and set planet focus to preferred focus depending on happiness"""
# TODO Assess need to set planet to preferred focus to improve happiness


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function seems missing a pass statement.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pass required in no other code is present. It just like close bracket for indent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do comments actually count as well? Cause there sure isn't any actual code within the function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments does not count. Function have docstring. It is part of function declaration and can be accessed in runtime via __doc__

1. Clarified function purpose by changing function names
use_planet_growth_specials  to set_planet_growth_specials
and use_planet_production_and_research_specials to
set_planet_production_and_research_specials.

2. Corrected naming of spSize to _spPop to indicate it is unused and
refers to population size, not planet size.

3. Expanded docstring of use_planet_production_and_research_specials to
clarify its use.

4. Removed dead duplicate error reporting code for concentration camp
focus setting.

5. Added pass to set_planet_happiness_foci placeholder function.
@LGM-Doyle
Copy link
Contributor Author

@Morlic-fo thanks for the review. I changed everything except the variable names, which I left the same as before the refactor.

@Morlic-fo
Copy link
Contributor

I have no further objections to merging this @Cjkjvfnby

@Cjkjvfnby Cjkjvfnby merged commit dbea229 into freeorion:master May 12, 2016
@LGM-Doyle LGM-Doyle deleted the refactor_ResourcesAI_py branch May 15, 2016 11:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
category:refactoring The Issue/PR describes or contains an improved implementation. component:AI The Issue/PR deals with the Python AI decision making code or affects it.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants