Git Product home page Git Product logo

loneparentsmodel.jl's Introduction

LoneParentsModel.jl

An initial implementation of an ABM aiming at a social and child care simulation.

Releases

  • V0.1 (11-05-2022): Initial concepts of agents, ABMs and a dummy exmaple are realized. Basic interfaces of the Package Agents.jl are imitated in order to fully exploit Agents.jl in future.
    • V0.1.1: Improved concept for an ABM and data structure for a simulation
    • V0.1.2: Improved concepts for Social Simulation, Social ABMs, started translation of LPM & further unit tests
  • V0.2 (30-05-2022): improved unification potentials with Agents.jl, improved agents subtypes, SocialABM with a constructor with an argument as a declare function, First realization of a multiABM concept, Initialization of the demography model
    • V0.2.1: unnecessary code removed, additional functionalities, exception handling, Unit testing (Person & House)
    • V0.2.2: Distributing properties among smaller ABMs, Separation between declaration and initialisation, utilisation of built-in exceptions, Agent person tuning + unit tests
    • v0.2.3: re-structure of examples, improved printing
    • v0.2.4: simulation concept
  • V0.3 (23.06.2022): Improved simulation concep, Flexibility for simulating same example with different implementation (via type traits), Population simulation considering death probabilities
    • v0.3.1: The abstract conceptual part of the code moved to MultiAgents.jl
    • v0.3.2: Kinship & BasicInfo modules for the type Person
  • V0.4 (25.08.2022): A new module LPM containing generic implementation. In contrary, MALPM makes use of LPM & MultiAgents.jl
  • V0.5 (20.09.2022): Female agents giving birth, generic XAgents and flat Main simulation for agent-based modeling without other packages, re-architecturing Simulate module and implementation patterns of incldued functions, Agent Person improved declaration and implementation, DRY concept for struct fields of Agent Person, Employing Parameters.jl
  • V0.6 (7.11.2022) : marraiges, guardians for orphans, socio-economoic status, social transition, setting parameters from files / flags, gui
    • v0.6.1 (12.11.2022) : LPMVERSION referring to release, few functions for computing population pyramid index

Source code structure (not updated)

  • /src
    • XAgents.jl : Basic concept of an Agent
    • /agents/* : Examples of agents
    • MultiABMs.jl : Basic concept of elementary ABMs
    • /abms/* : Examples of abms and useful step functions
    • LoneParentsModel.jl : Main modules for realizing LPM.jl
    • /lpm submodules for declaring, initializing and simulating lpm
    • Utilities.jl : util routines used across the library
  • /tests
  • MainLPM.jl : Initial (empty) translation of LPM
  • loadLibsPath : load paths to internal libraries

Running the code

See the head documentation of RunTests.jl and Main*.jl

loneparentsmodel.jl's People

Contributors

atiyahelsheikh avatar mhinsch avatar

Watchers

 avatar

loneparentsmodel.jl's Issues

implement MaternityBlock

Agent module related to pregnancy and maternity leave (only maternitystatus and monthssincebirth for now).

Urgent & priority labels

I propose the following label. Please use this label only when urgently needed. Otherwise, no need for stating when something is going to be processed.

Import vs. Using

The book "Hands-on design patterns and best practices with Julia" recommends to employ import when a functionality is being extended, otherwise to employ using by default. This is so far the way I have followed.

coding guidelines II

While I very much prefer tabs over spaces, I can live with the latter. They should be used consistently, though. Currently some source files use 2 and some 4 spaces. Can we agree on 4?

generic transition interface

All population-wide transitions essentially do the same, so it makes sense to unify their interface. I have implemented that here (in Main.jl, Utilities.jl and socialTransition.jl): A function applyTransitions (for now in Utilities.jl, should maybe be in some sort of Population module) applies a transition function (which operates on Person) and the usual parameters (model, verbose, parameters, etc.) to all elements of an iterable. The specific simulation module (in this case for example socialTransition) also provides a predicate (here selectSocialTransition) to select the agents the transition needs to be applied to.

styleguide: only use ?: to select values

I think the use of ?: as an if-replacement is getting out of hand. It's really easy to skip over these lines and if you are - like me - used to the C/C++ use of ?: it automatically registers as non-modifying. There are some cases where it's nicer to use (modifying) ?: than if, but especially if one of the branches is empty I would really prefer it if we could stick with the boring option.

better modularity?

Mixers.jl makes it possible to directly "assemble" a struct from parts:

@mix struct Bla1
    x :: Int
end

@mix struct Bla2
    y :: Float64
end

@Bla1 @Bla2 struct Meep
    z :: String
end

Meep will then have three fields, x, y and z (with the corresponding types). This would make the implementation of modular agent types a lot more lightweight as the combined type can be treated like a plain struct with all variables as direct members.

On the other hand we would lose the clear separation between modules, so it would be easier to for example accidentally access a BasicInfo property in Family.

Anyway, I don't have a clear opinion on this yet, but I thought it might be worth thinking about.

Potential simplification

Some pieces of code is subject to simplification.

For instance:

            setDead!(person) 
            resetHouse!(person)
            isSingle(person) ?
                nothing :  
                resolvePartnership!(partner(person),person)

the last two statements can be moved to the function setDead!()

Also, the Person constructor can be improved so that the client does not take care setting up kinship relationship. My current, Person inner cor looks as

function Person(pos, info, kinship)
        person = new(getIDCOUNTER(),pos,info,kinship)
        if !undefined(pos)
            addOccupant!(pos, person)
        end

        kinship.father != nothing ? addChild!(kinship.father,person) : nothing
        kinship.mother != nothing ? addChild!(kinship.mother,person) : nothing  
        if kinship.partner != nothing
            resetPartner!(kinship.partner)
            partner.partner = person 
        end 
        if length(kinship.children) > 0
            for child in kinship.children
                setAsParentChild!(person,child)
            end
        end 

        person  
    end 

instead of

function Person(pos, info, kinship)
        person = new(getIDCOUNTER(),pos,info,kinship)
        if !undefined(pos)
            addOccupant!(pos, person)
        end
        person  
    end 

This also simplifies the implementation of DoBirths

p.s. : will also modify the ? to if

Julia 1.8.1 new features

In the context of issue #79 , I thought to experiment with Julia new features, particularly declaring some fields to mutable struct to be const. It did not improve the performance in this case, but it is a nice feature. Another feature is the ability to declare global types.

Let me know if nothing against to use this feature

p.s. May be you know the UpdateJulia package

LPM depends on AbstractAgent/AbstractXAgent

As far as I can tell this this the last non-trivial dependency on MultiABM in LPM. Unfortunately I can't see a simple way to get rid of this (at least if we want to maintain compatibility of LPM with Agents.jl).

I can see two ways to do this:

  1. Use a macro to make the superclass of all agent types generic (sadly this is not possible in plain Julia code, struct T{S} <: S does not work).
  2. A multi-part process along these lines:
    • make Create/Initialize/etc. agent-type agnostic (maybe not a bad idea anyway), by using type parameters where necessary (function arguments declared as ::Type{T} bind type T and can be called using the plain type, e.g. Int).
    • remove dependency on XAgents from Demography
    • move existing XAgents.jl to MALPMXAgents.jl and create generic XAgents.jl that declares dummy AbstractAgent and AbstractXAgent types
    • in modules LPM/MALPM include the respective XAgents module

remove all abstract types from properties

This is not a good idea:

mutable struct Kinship # <: DynamicStruct 
  father::Union{AbstractPerson,Nothing}
  mother::Union{AbstractPerson,Nothing} 
  partner::Union{AbstractPerson,Nothing}
  children::Vector{AbstractPerson}
end 

formalised structure for assumptions

Come up with a way to add assumptions to the code so that:

  • they will automatically be tested
  • they can include additional information
  • all assumptions can easily be extracted from the code

efficient parameters

Currently parameters are of type Dict{Symbol, Any}, which is extremely inefficient, as a) Dicts are slow and b) the value type is Any and therefore potentially all code that uses parameters becomes type unstable.
The syntax of parameter access can be changed to pars.value without any loss in generality (if desired property access can be overloaded so that a Dict could still be used as a backend). This way the simulation functions themselves can remain agnostic as to which type parameters have and the frontend can decide which implementation to use.
Generally, I think, however, that there are few situations where a Dict is the best choice.

remove unnecessary types

At the moment most functions seem to have explicitly typed parameters. Unless it is specifically known that a function only works for one type it is usually better to omit types.

consistent naming convention

Currently camelCase and snake_case are both used. Personally I prefer CamelCase for types and snake_case for functions and variables, but whatever we choose it should be consistent.

Simulation speed of MA-dependent Main

The simulation speed of MA-based main including doDeaths & doBirths is slow ca. 10 secs (40 secs when assumptions are being checked) & memory allocation is more than 1.5 GB (5 GB). So I will attempt (some of) the following

  • #74 & #65
  • cheap manipulation of the data structure within MA or
  • Optimised versions of the algorithms
  • Linking with Agents.jl

rename declare?

This is obviously bikeshedding, but declaration has a very specific meaning in the context of statically typed languages, so maybe we could use create or something like that.

Push!(LOAD_PATH,...)

Having Push!(LOAD_PATH,..) in the code is not good.

When executing the main script couple of times (which is usually my case using REPL and Revise), LOAD_PATH is always incremented with redundant directories. Also the file path is restrictive and the user is enforced to follow a specific directory structure for other Julia packages (e.g. MultiAgents.jl).

The same arguments apply to the code in Person.jl
push!(LOAD_PATH, @__DIR__)

factor out iteration in model functions

Not important now, but in the medium term it would be good to split model functions into the iteration part, that picks the agents to be modified and the actual modification that operates on single agents. E.g. (simplified example):

function doAgeTransitions!(people, model, pars)
    for p in people
        ageTransition!(p, pars)
    end
end

function ageTransition!(person, pars)
     # all the important bits go here
end

That makes the model easier to read and modify (IMO) and would be needed anyway if we ever want to do an event-based version.

What does *M stand for?

e.g. KinshipM and BasicInfoM
Module?

If this is the case, to my understanding you'd like to re-structure the code into more modules, e.g. Module Person. In this case, the naming convention would be enough to deduce if a file corresponds to a module or not.

Edit: Yes in this case, the module name should be different than the type name

file names

more bikeshedding ;-) : I think module files should have uppercase and non-modules (i.e. files that are just included) lower-case names.

Decompose Simulate module into several source files

Module LPM/Simulate.jl is becoming larger and larger. The following is proposed:

module Simulate 
       ...
       include(simulate/helpfunc.jl")
       include("simulate/deaths.jl")
       include("simulate/births.jl") 
       include("simulate/marraiges.jl")     
       ...
end # Simulate 

think about export/using

This has been discussed in issue #73 before, but I wanted to give it its own issue. As I see it, using explicit imports (i.e. Using SomeModule: someFunction) has several advantages and disadvantages:
Advantages

  • Makes it clear which parts of an API are actually used.
  • Avoids cluttering the namespace with all exports of a module.

Disadvantages

  • Maintaining the list of imports can be quite tedious.
  • Maintaining the list of imports can be error-prone (it's easy to forget to remove an import when a function is no longer being called).
  • It circumvents export control. Every symbol in a module can be imported if mentioned explicitly in a Using statement, no matter whether it was exported or not. At the very least this makes exports in project-internal modules completely pointless.

My conclusion would be that we should drop explicit imports at least for internal modules.

find a solution/replacement for MultiAgents

Currently some general functionality is still imported from MultiAgents. If LoneParentsModel.jl is supposed to be completely independent of MultiAgents.jl these imports should be removed. It would be nice to find a solution that doesn't duplicate effort, though.

more DRY code?

At the moment, with getters/setters, imports, exports and delegation, adding one property to one of the agent blocks requires 10 largely mechanical, repeated additions (the property itself, getter/setter, export in Block, import in Person, export in Person, 2xdelegate in Person, 3 constructors), which seems a little excessive.
I'm not sure what the best solution is, but the current state is not ideal, I think.

@todo macro

I thought since @assertion is under consideration, I believe @todo macro could be also similar and useful. Basically, @todo export reminder information about functionalities that need to be implemented. This is particularly useful for functions that cannot be implemented right away.

A use-case (just as an example) , in the implementation of doDeath!(*) one makes use of many attributes of attributes of structs, e.g. person.info.alive. However, to make this function more generic, it would be better to have a function isAlive(person). I don't want to implement it myself right away myself, since I know you are working on that part of the code.

Currently, I am expressing the need of that function using the following piece of code

...
false ? isAlive(person) : nothing 
# or 
age = false ? age(person) : person.info.age   
... 

Statistics collections to be moved out of LPM/Simulate/*

Apparently since there are nice ways to collect stats during a simulation, and also if working with lazy iterators, counting would be needed, then it seems better to not overload basic simulation functions such a responsibility.

agestepAlive should not be needed

Maybe it is actually required, but in first approximation I would say that if a population has agents that are not alive mixed in something is wrong.

automatic code generation for unifying coding guidelines

I feel the task of enhancing a package X with code like

[function] does_something() ... 

with an additional package Y that reads X and generates

module Y

   using X 

   doesSomething(*) = does_something(*)

end 

Is somehow manageable? what do you think?

related to issue #16

more general solution for modular agents?

If we split agents into modules, such as kinship, demography, health, etc. functions that operate on these modules won't automatically work on composite agents. For example add_child!(person) would be defined with person::Kinship in mind. An agent that contains the kinship module would then have to forward the function call: add_child!(person) = add_child!(person.kinship). This is very annoying at best and a source for errors at worst. Sadly Julia has neither mixins nor multiple inheritance so we have to find a solution ourselves.

I have a couple of ideas how to solve this (from simple naming conventions to meta-programming trickery) but in any case it would be nice to have a general solution.

get rid of population.properties

Having a universal type ABM sounds attractive, but I think in practice there is little it can buy. On the other hand the same efficiency argument applies as with parameters.
As with parameters using dot syntax to access global simulation properties within the simulation code doesn't hurt but has the potential to allow for a much more efficient implementation.

use accessor methods for agent fields

With modular agents in place it might be better to avoid assuming any internal layout in agents altogether. So, for example instead of agent.info.age use age(agent) and instead of agent.info.age = 1 use setAge!(agent, 1).

To Delete SomeUtil.jl

SomeUtil.jl was introduced because few routines could be used from both LPM.jl and MA.jl
Due to the simplicity of the included routine, I would simply move whatever needed to be under LPM/Utilites.jl & MA/Util.jl

naming convention for getters/setters

Not terribly important, but just for the sake of completeness. My macros generate property(obj) and property!(obj). If you'd rather use something more explicit (e.g. getProperty, setProperty!) now would be the time to say so (personally, I prefer the more concise version, but it's not something I can't live without).

remove dependencies on MultiAgents, part II

This is complete list of symbols that are still imported from MultiAgents. They should be moved to MainLPMMultiAgents.jl (or similar):

  • ABM
  • MultiABM
  • MABMSimulation
  • run!
  • verify
  • read2DArray
  • date2yearsmonth
  • removefirst!
  • subtract!
  • add_agent!
  • dummystep
  • defaultprestep!
  • defaultpoststep!
  • startTime
  • dt
  • attach_agent_step!
  • attach_pre_model_step!
  • attach_post_model_step!
  • setup!
  • allagents
  • nagents
  • initial_connect!
  • attach2DData!
  • AbstractExample
  • AbstractAgent
  • AbstractXAgent
  • getIDCOUNTER
  • kill_agent!

setAsParentChild! is wrong

It includes the following statement

setParent!(child, father) # father is not defined

RunTests.jl needs to be modified to detect such bugs.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.