Esempio n. 1
0
def test_variableset():
    v1 = VariableSet()
    v2 = VariableSet()

    assert len(v1) == 0
    assert len(v2) == 0

    with pytest.raises(KeyError):
        v1["beta[1]"]

    v1["beta[1]"] = 0.5

    assert v1["beta[1]"] == 0.5
    assert len(v1) == 1

    assert v1 != v2

    v2["beta[1]"] = 0.5

    assert v1 == v2

    d = Disease.load("ncov")

    p = Parameters()
    p.set_disease("ncov")

    assert p.disease_params == d

    assert p.disease_params.beta[1] != 0.5

    p = p.set_variables(v1)

    assert p.disease_params.beta[1] == 0.5
Esempio n. 2
0
def test_set_variables():
    d = Disease.load("ncov")

    p = Parameters()
    p.set_disease("ncov")

    assert p.disease_params == d

    variables = VariableSet(l1)

    p2 = p.set_variables(variables)

    assert p.disease_params == d
    assert p2.disease_params != d

    assert p2.disease_params.beta[2] == 0.9
    assert p2.disease_params.beta[3] == 0.93
    assert p2.disease_params.progress[1] == 0.18
    assert p2.disease_params.progress[2] == 0.92
    assert p2.disease_params.progress[3] == 0.90

    variables = VariableSet(l0)

    p3 = p2.set_variables(variables)

    assert p3.disease_params.beta[2] == 0.95
    assert p3.disease_params.beta[3] == 0.95
    assert p3.disease_params.progress[1] == 0.19
    assert p3.disease_params.progress[2] == 0.91
    assert p3.disease_params.progress[3] == 0.91
Esempio n. 3
0
def test_parameterset():
    vars0 = VariableSet(variables=l0)

    assert vars0.repeat_index() == 1

    for key, value in l0.items():
        assert key in vars0.variable_names()
        assert value in vars0.variable_values()
        assert vars0[key] == value

    vars1 = VariableSet(l1, 2)

    assert vars1.repeat_index() == 2

    for key, value in l1.items():
        assert key in vars1.variable_names()
        assert value in vars1.variable_values()
        assert vars1[key] == value

    assert vars0.fingerprint() != vars1.fingerprint()
    assert vars0.fingerprint() != vars0.fingerprint(include_index=True)
    assert vars1.fingerprint() != vars1.fingerprint(include_index=True)

    variables = VariableSets()
    assert len(variables) == 0

    variables.append(vars0)
    variables.append(vars1)

    assert len(variables) == 2
    assert variables[0] == vars0
    assert variables[1] == vars1

    variables = variables.repeat(5)

    assert len(variables) == 10

    for i in range(0, 5):
        idx0 = 2 * i
        idx1 = idx0 + 1

        print(f"{idx0} : {variables[idx0]} vs {l0}")
        print(f"{idx1} : {variables[idx1]} vs {l1}")

        assert variables[idx0].variables() == l0
        assert variables[idx1].variables() == l1
        assert variables[idx0].fingerprint() == vars0.fingerprint()
        assert variables[idx1].fingerprint() == vars1.fingerprint()
        assert variables[idx0].repeat_index() == i + 1
        assert variables[idx1].repeat_index() == i + 1
Esempio n. 4
0
def test_make_compatible():
    v1 = VariableSet()
    v1["beta[1]"] = 0.95
    v1["beta[2]"] = 0.9

    v2 = VariableSet(repeat_index=5)
    v2["beta[2]"] = 0.5

    v3 = v2.make_compatible_with(v1)
    assert v3["beta[1]"] == v1["beta[1]"]
    assert v2["beta[2]"] == v2["beta[2]"]
    assert v3.repeat_index() == v2.repeat_index()

    with pytest.raises(ValueError):
        v1.make_compatible_with(v2)
Esempio n. 5
0
def test_adjustable():

    params = Parameters.load()
    params.set_disease("lurgy")

    variables = VariableSet()
    variables["user.something[5]"] = 0.5
    variables["user.something[2]"] = 0.3
    variables["user.another[1]"] = 0.8
    variables["user.flag"] = True  # this will be converted to 1.0

    variables["beta[2]"] = 0.2
    variables["too_ill_to_move[1]"] = 0.15
    variables["progress[0]"] = 0.99
    variables["contrib_foi[4]"] = 0.45

    variables["length_day"] = 0.75

    variables["UV"] = 0.4

    with pytest.raises(KeyError):
        variables["broken"] = 0.9

    with pytest.raises(KeyError):
        variables["Beta[2]"] = 0.8

    variables.adjust(params)

    print(params)
    print(params.disease_params)
    print(params.user_params)

    assert variables in params.adjustments

    print(params.adjustments)

    assert params.user_params["something"][5] == 0.5
    assert params.user_params["something"][2] == 0.3
    assert params.user_params["another"][1] == 0.8
    assert params.user_params["flag"] == 1.0

    assert params.disease_params.beta[2] == 0.2
    assert params.disease_params.too_ill_to_move[1] == 0.15
    assert params.disease_params.progress[0] == 0.99
    assert params.disease_params.contrib_foi[4] == 0.45

    assert params.length_day == 0.75
    assert params.UV == 0.4
Esempio n. 6
0
def cli():
    """Main function for the command line interface. This does one of three
       things:

       1. If this is the main process, then it parses the arguments and
          runs and manages the jobs

       2. If this is a worker process, then it starts up and waits for work

       3. If this is a supervisor process, then it query the job scheduling
          system for information about the compute nodes to use, and will then
          set up and run a manager (main) process that will use those
          nodes to run the jobs
    """
    from metawards.utils import Console

    # get the parallel scheme now before we import any other modules
    # so that it is clear if mpi4py or scoop (or another parallel module)
    # has been imported via the required "-m module" syntax
    parallel_scheme = get_parallel_scheme()

    if parallel_scheme == "mpi4py":
        from mpi4py import MPI
        comm = MPI.COMM_WORLD
        nprocs = comm.Get_size()
        rank = comm.Get_rank()

        if rank != 0:
            # this is a worker process, so should not do anything
            # more until it is given work in the pool
            Console.print(f"Starting worker process {rank+1} of {nprocs-1}...")
            return
        else:
            Console.print("Starting main process...")

    elif parallel_scheme == "scoop":
        Console.print("STARTING SCOOP PROCESS")

    import sys

    args, parser = parse_args()

    if not args.already_supervised:
        hostfile = get_hostfile(args)
        if hostfile:
            # The user has asked to run a parallel job - this means that this
            # process is the parallel supervisor
            if args.mpi:
                mpi_supervisor(hostfile, args)
                return
            elif args.scoop:
                scoop_supervisor(hostfile, args)
                return

            # neither is preferred - if scoop is installed then use that
            try:
                import scoop  # noqa - disable unused warning
                have_scoop = True
            except Exception:
                have_scoop = False

            if have_scoop:
                scoop_supervisor(hostfile, args)
                return

            # do we have MPI?
            try:
                import mpi4py  # noqa - disable unused warning
                have_mpi4py = True
            except Exception:
                have_mpi4py = False

            if have_mpi4py:
                mpi_supervisor(hostfile, args)
                return

            # we don't have any other option, just keep going and
            # use multiprocessing - in this case we don't need a
            # supervisor and this is the main process

    # This is now the code for the main process

    # WE NEED ONE OF these listed options;
    should_run = False

    for arg in [
            args.input, args.repeats, args.disease, args.additional,
            args.model, args.iterator, args.extractor, args.demographics,
            args.mixer, args.mover
    ]:
        if arg is not None:
            should_run = True
            break

    if not should_run:
        parser.print_help(sys.stdout)
        sys.exit(0)

    if args.repeats is None:
        args.repeats = [1]

    # import the parameters here to speed up the display of help
    from metawards import Parameters, Network, Population, print_version_string

    # print the version information first, so that there is enough
    # information to enable someone to reproduce this run
    print_version_string()

    Console.rule("Initialise")

    if args.input:
        # get the line numbers of the input file to read
        if args.line is None or len(args.line) == 0:
            linenums = None
            Console.print(f"* Using parameters from all lines of {args.input}",
                          markdown=True)
        else:
            from metawards.utils import string_to_ints
            linenums = string_to_ints(args.line)

            if len(linenums) == 0:
                Console.error(f"You cannot read no lines from {args.input}?")
                sys.exit(-1)
            elif len(linenums) == 1:
                Console.print(
                    f"* Using parameters from line {linenums[0]} of "
                    f"{args.input}",
                    markdown=True)
            else:
                Console.print(
                    f"* Using parameters from lines {linenums} of "
                    f"{args.input}",
                    markdown=True)

        from metawards import VariableSets, VariableSet
        variables = VariableSets.read(filename=args.input,
                                      line_numbers=linenums)
    else:
        from metawards import VariableSets, VariableSet
        # create a VariableSets with one null VariableSet
        variables = VariableSets()
        variables.append(VariableSet())

    nrepeats = args.repeats

    if nrepeats is None or len(nrepeats) < 1:
        nrepeats = [1]

    if len(nrepeats) > 1 and len(variables) != len(nrepeats):
        Console.error(f"The number of repeats {len(nrepeats)} must equal the "
                      f"number of adjustable variable lines {len(variables)}")
        raise ValueError("Disagreement in the number of repeats and "
                         "adjustable variables")

    # ensure that all repeats are >= 0
    nrepeats = [0 if int(x) < 0 else int(x) for x in nrepeats]

    if sum(nrepeats) == 0:
        Console.error(f"The number of the number of repeats is 0. Are you "
                      f"sure that you don't want to run anything?")
        raise ValueError("Cannot run nothing")

    if len(nrepeats) == 1 and nrepeats[0] == 1:
        Console.print("* Performing a single run of each set of parameters",
                      markdown=True)
    elif len(nrepeats) == 1:
        Console.print(
            f"* Performing {nrepeats[0]} runs of each set of parameters",
            markdown=True)
    else:
        Console.print(
            f"* Performing {nrepeats} runs applied to the parameters",
            markdown=True)

    variables = variables.repeat(nrepeats)

    # working out the number of processes and threads...
    from metawards.utils import guess_num_threads_and_procs
    (nthreads,
     nprocs) = guess_num_threads_and_procs(njobs=len(variables),
                                           nthreads=args.nthreads,
                                           nprocs=args.nprocs,
                                           parallel_scheme=parallel_scheme)

    Console.print(
        f"\n* Number of threads to use for each model run is {nthreads}",
        markdown=True)

    if nprocs > 1:
        Console.print(
            f"* Number of processes used to parallelise model "
            f"runs is {nprocs}",
            markdown=True)
        Console.print(
            f"* Parallelisation will be achieved using {parallel_scheme}",
            markdown=True)

    # sort out the random number seed
    seed = args.seed

    if seed is None:
        import random
        seed = random.randint(10000, 99999999)

    if seed == 0:
        # this is a special mode that a developer can use to force
        # all jobs to use the same random number seed (15324) that
        # is used for comparing outputs. This should NEVER be used
        # for production code
        Console.warning("Using special mode to fix all random number"
                        "seeds to 15324. DO NOT USE IN PRODUCTION!!!")
    else:
        Console.print(f"* Using random number seed {seed}", markdown=True)

    # get the starting day and date
    start_day = args.start_day

    if start_day < 0:
        raise ValueError(f"You cannot use a start day {start_day} that is "
                         f"less than zero!")

    start_date = None

    if args.start_date:
        try:
            from dateparser import parse
            start_date = parse(args.start_date).date()
        except Exception:
            pass

        if start_date is None:
            from datetime import date
            try:
                start_date = date.fromisoformat(args.start_date)
            except Exception as e:
                raise ValueError(f"Cannot interpret a valid date from "
                                 f"'{args.start_date}'. Error is "
                                 f"{e.__class__} {e}")

    if start_date is None:
        from datetime import date
        start_date = date.today()

    Console.print(f"* Day zero is {start_date.strftime('%A %B %d %Y')}",
                  markdown=True)

    if start_day != 0:
        from datetime import timedelta
        start_day_date = start_date + timedelta(days=start_day)
        Console.print(f"Starting on day {start_day}, which is "
                      f"{start_day_date.strftime('%A %B %d %Y')}")
    else:
        start_day_date = start_date

    # now find the MetaWardsData repository as this will be needed
    # for the repeat command line too
    (repository,
     repository_version) = Parameters.get_repository(args.repository)

    Console.print(f"* Using MetaWardsData at {repository}", markdown=True)

    if repository_version["is_dirty"]:
        Console.warning("This repository is dirty, meaning that the data"
                        "has not been committed to git. This may make "
                        "this calculation very difficult to reproduce")

    # now work out the minimum command line needed to repeat this job
    args.seed = seed
    args.nprocs = nprocs
    args.nthreads = nthreads
    args.start_date = start_date.isoformat()
    args.repository = repository

    # also print the source of all inputs
    import configargparse
    Console.rule("Souce of inputs")
    p = configargparse.get_argument_parser("main")
    Console.print(p.format_values())

    # print out the command used to repeat this job
    repeat_cmd = "metawards"

    for key, value in vars(args).items():
        if value is not None:
            k = key.replace("_", "-")

            if isinstance(value, bool):
                if value:
                    repeat_cmd += f" --{k}"
            elif isinstance(value, list):
                repeat_cmd += f" --{k}"
                for val in value:
                    v = str(val)
                    if " " in v:
                        repeat_cmd += f" '{v}''"
                    else:
                        repeat_cmd += f" {v}"
            else:
                v = str(value)
                if " " in v:
                    repeat_cmd += f" --{k} '{v}''"
                else:
                    repeat_cmd += f" --{k} {v}"

    Console.rule("Repeating this run")
    Console.print("To repeat this job use the command;")
    Console.command(repeat_cmd)
    Console.print("Or alternatively use the config.yaml file that will be "
                  "written to the output directory and use the command;")
    Console.command("metawards -c config.yaml")

    # load all of the parameters
    try:
        params = Parameters.load(parameters=args.parameters)
    except Exception as e:
        Console.warning(
            f"Unable to load parameter files. Make sure that you have "
            f"cloned the MetaWardsData repository and have set the "
            f"environment variable METAWARDSDATA to point to the "
            f"local directory containing the repository, e.g. the "
            f"default is $HOME/GitHub/MetaWardsData")
        raise e

    # should we profile the code? (default no as it prints a lot)
    profiler = None

    if args.no_profile:
        profiler = None
    elif args.profile:
        from metawards.utils import Profiler
        profiler = Profiler()

    # load the disease and starting-point input files
    Console.rule("Disease")
    if args.disease:
        params.set_disease(args.disease)
    else:
        params.set_disease("ncov")

    Console.rule("Model data")
    if args.model:
        params.set_input_files(args.model)
    else:
        params.set_input_files("2011Data")

    # load the user-defined custom parameters
    Console.rule("Custom parameters and seeds")
    if args.user_variables:
        custom = VariableSet.read(args.user_variables)
        Console.print(f"Adjusting variables to {custom}")
        custom.adjust(params)
    else:
        Console.print("Not adjusting any parameters...")

    # read the additional seeds
    if args.additional is None or len(args.additional) == 0:
        Console.print("Not using any additional seeds...")
    else:
        for additional in args.additional:
            Console.print(f"Loading additional seeds from {additional}")
            params.add_seeds(additional)

    # what to do with the 0 state?
    stage_0 = "R"

    if args.disable_star:
        Console.print("Disabling the * state. Stage 0 is the one and "
                      "only E state.")
        stage_0 = "disable"
    elif args.star_is_E:
        Console.print("Setting the * state as an additional E state.")
        stage_0 = "E"
    else:
        Console.print("Setting the * state as an additional R state.")
        stage_0 = "R"

    params.stage_0 = stage_0

    # extra parameters that are set
    params.UV = args.UV

    # set these extra parameters to 0
    params.static_play_at_home = 0
    params.play_to_work = 0
    params.work_to_play = 0
    params.daily_imports = 0.0

    Console.rule("Parameters")
    Console.print(params, markdown=True)

    # the size of the starting population
    population = Population(initial=args.population,
                            date=start_day_date,
                            day=start_day)

    Console.rule("Building the network")
    network = Network.build(params=params,
                            population=population,
                            max_nodes=args.max_nodes,
                            max_links=args.max_links,
                            profiler=profiler)

    if args.demographics:
        from metawards import Demographics
        Console.rule("Specialising into demographics")
        demographics = Demographics.load(args.demographics)
        Console.print(demographics)

        network = network.specialise(demographics,
                                     profiler=profiler,
                                     nthreads=nthreads)

    Console.rule("Preparing to run")
    from metawards import OutputFiles
    from metawards.utils import run_models

    outdir = args.output

    if outdir is None:
        outdir = "output"

    if args.force_overwrite_output:
        prompt = None
    else:
        from metawards import input

        def prompt(x):
            return input(x, default="y")

    auto_bzip = True

    if args.auto_bzip:
        auto_bzip = True
    elif args.no_auto_bzip:
        auto_bzip = False

    if args.iterator:
        iterator = args.iterator
    else:
        iterator = None

    if args.extractor:
        extractor = args.extractor
    else:
        extractor = None

    if args.mixer:
        mixer = args.mixer
    else:
        mixer = None

    if args.mover:
        mover = args.mover
    else:
        mover = None

    with OutputFiles(outdir,
                     force_empty=args.force_overwrite_output,
                     auto_bzip=auto_bzip,
                     prompt=prompt) as output_dir:
        # write the config file for this job to output/config.yaml
        Console.rule("Running the model")
        CONSOLE = output_dir.open("console.log")
        Console.save(CONSOLE)

        lines = []
        max_keysize = None

        for key, value in vars(args).items():
            if max_keysize is None:
                max_keysize = len(key)
            elif len(key) > max_keysize:
                max_keysize = len(key)

        for key, value in vars(args).items():
            if value is not None:
                key = key.replace("_", "-")
                spaces = " " * (max_keysize - len(key))

                if isinstance(value, bool):
                    if value:
                        lines.append(f"{key}:{spaces} true")
                    else:
                        lines.append(f"{key}:{spaces} false")
                elif isinstance(value, list):
                    s_value = [str(x) for x in value]
                    lines.append(f"{key}:{spaces} [ {', '.join(s_value)} ]")
                else:
                    lines.append(f"{key}:{spaces} {value}")

        CONFIG = output_dir.open("config.yaml", auto_bzip=False)
        lines.sort(key=str.swapcase)
        CONFIG.write("\n".join(lines))
        CONFIG.write("\n")
        CONFIG.flush()
        CONFIG.close()
        lines = None

        result = run_models(network=network,
                            variables=variables,
                            population=population,
                            nprocs=nprocs,
                            nthreads=nthreads,
                            seed=seed,
                            nsteps=args.nsteps,
                            output_dir=output_dir,
                            iterator=iterator,
                            extractor=extractor,
                            mixer=mixer,
                            mover=mover,
                            profiler=profiler,
                            parallel_scheme=parallel_scheme)

        if result is None or len(result) == 0:
            Console.print("No output - end of run")
            return 0

        Console.rule("End of the run", style="finish")

        Console.save(CONSOLE)

    return 0
Esempio n. 7
0
def test_pathway():
    demographics = Demographics.load(demographics_json)
    assert len(demographics) == 2

    disease_home = Disease.load(filename=home_json)
    disease_super = Disease.load(filename=super_json)

    assert demographics[1].disease is None
    assert demographics[0].disease == disease_super

    params = Parameters.load()
    params.set_disease(disease_home)
    params.set_input_files("single")
    params.add_seeds("ExtraSeedsOne.dat")

    network = Network.build(params)

    print(network.params.disease_params)
    print(disease_home)

    assert network.params.disease_params == disease_home

    network = network.specialise(demographics)

    print(network.params.disease_params)
    print(disease_home)

    assert network.params.disease_params == disease_home

    print(network.subnets[1].params.disease_params)
    print(disease_home)

    assert network.subnets[1].params.disease_params == disease_home

    print(network.subnets[0].params.disease_params)
    print(disease_super)

    assert network.subnets[0].params.disease_params == disease_super

    infections = network.initialise_infections()

    assert infections.N_INF_CLASSES == disease_home.N_INF_CLASSES()

    assert \
        infections.subinfs[1].N_INF_CLASSES == disease_home.N_INF_CLASSES()

    assert \
        infections.subinfs[0].N_INF_CLASSES == disease_super.N_INF_CLASSES()

    assert disease_super.N_INF_CLASSES() != disease_home.N_INF_CLASSES()

    outdir = os.path.join(script_dir, "test_pathway")

    with OutputFiles(outdir, force_empty=True, prompt=None) as output_dir:
        results = network.copy().run(population=Population(),
                                     output_dir=output_dir,
                                     mixer=mix_evenly,
                                     nthreads=1,
                                     seed=36538943)

    # using one thread, but if use 2 then have a system crash after
    # any other test that uses the big network. This is because we
    # have intialised some global data that assumes a large network,
    # which then fails for the small network

    OutputFiles.remove(outdir, prompt=None)

    print(results[-1])
    print(results[-1].initial)

    expected = Population(susceptibles=519,
                          latent=0,
                          total=0,
                          recovereds=481,
                          n_inf_wards=0,
                          day=90)

    print(expected)

    assert results[-1].has_equal_SEIR(expected)
    assert results[-1].day == expected.day

    with OutputFiles(outdir, force_empty=True, prompt=None) as output_dir:
        results = network.copy().run(population=Population(),
                                     output_dir=output_dir,
                                     mixer=mix_evenly,
                                     nthreads=1,
                                     seed=36538943)

    OutputFiles.remove(outdir, prompt=None)

    print(results[-1])
    print(results[-1].initial)

    print(expected)

    assert results[-1].has_equal_SEIR(expected)
    assert results[-1].day == expected.day

    variables = VariableSet()

    print("\nUpdate with null variables")
    oldparams = network.params
    params = network.params.set_variables(variables)
    network.update(params)

    assert oldparams == network.params

    print(network.params.disease_params)
    print(disease_home)

    assert network.params.disease_params == disease_home

    print(network.subnets[1].params.disease_params)
    print(disease_home)

    assert network.subnets[1].params.disease_params == disease_home

    print(network.subnets[0].params.disease_params)
    print(disease_super)

    assert network.subnets[0].params.disease_params == disease_super

    infections = network.initialise_infections()

    assert infections.N_INF_CLASSES == disease_home.N_INF_CLASSES()

    assert \
        infections.subinfs[1].N_INF_CLASSES == disease_home.N_INF_CLASSES()

    assert \
        infections.subinfs[0].N_INF_CLASSES == disease_super.N_INF_CLASSES()

    assert disease_super.N_INF_CLASSES() != disease_home.N_INF_CLASSES()

    outdir = os.path.join(script_dir, "test_pathway")

    with OutputFiles(outdir, force_empty=True, prompt=None) as output_dir:
        results = network.copy().run(population=Population(),
                                     output_dir=output_dir,
                                     mixer=mix_evenly,
                                     nthreads=1,
                                     seed=36538943)

    OutputFiles.remove(outdir, prompt=None)

    print(results[-1])
    print(expected)

    assert results[-1].has_equal_SEIR(expected)
    assert results[-1].day == expected.day