diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 84b2d32..4949492 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - julia-version: ['1.9'] + julia-version: ['1.11'] julia-arch: [x64, x86] #os: [ubuntu-latest, windows-latest, macOS-latest] os: [macOS-latest] diff --git a/Manifest.toml b/Manifest.toml index 424c21c..087f14b 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -1,8 +1,8 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.9.2" +julia_version = "1.11.7" manifest_format = "2.0" -project_hash = "0cf92eab662d0db09126fad16e530719c51a95e2" +project_hash = "068ab2105e07a40d4eed033981d4284856565852" [[deps.ANSIColoredPrinters]] git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c" @@ -11,42 +11,44 @@ version = "0.0.1" [[deps.ArgTools]] uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" -version = "1.1.1" +version = "1.1.2" [[deps.ArnoldiMethod]] deps = ["LinearAlgebra", "Random", "StaticArrays"] -git-tree-sha1 = "62e51b39331de8911e4a7ff6f5aaf38a5f4cc0ae" +git-tree-sha1 = "d57bd3762d308bded22c3b82d033bff85f6195c6" uuid = "ec485272-7323-5ecc-a04f-4719b315124d" -version = "0.2.0" +version = "0.4.0" [[deps.Artifacts]] uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" +version = "1.11.0" [[deps.Base64]] uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +version = "1.11.0" [[deps.CSV]] deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "PrecompileTools", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings", "WorkerUtilities"] -git-tree-sha1 = "44dbf560808d49041989b8a96cae4cffbeb7966a" +git-tree-sha1 = "deddd8725e5e1cc49ee205a1964256043720a6c3" uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" -version = "0.10.11" +version = "0.10.15" [[deps.CodecZlib]] deps = ["TranscodingStreams", "Zlib_jll"] -git-tree-sha1 = "02aa26a4cf76381be7f66e020a3eddeb27b0a092" +git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9" uuid = "944b1d66-785c-5afd-91f1-9de20f533193" -version = "0.7.2" +version = "0.7.8" [[deps.Combinatorics]] -git-tree-sha1 = "08c8b6831dc00bfea825826be0bc8336fc369860" +git-tree-sha1 = "8010b6bb3388abe68d95743dcbea77650bb2eddf" uuid = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" -version = "1.0.2" +version = "1.0.3" [[deps.Compat]] -deps = ["UUIDs"] -git-tree-sha1 = "e460f044ca8b99be31d35fe54fc33a5c33dd8ed7" +deps = ["TOML", "UUIDs"] +git-tree-sha1 = "9d8a54ce4b17aa5bdce0ea5c34bc5e7c340d16ad" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "4.9.0" +version = "4.18.1" weakdeps = ["Dates", "LinearAlgebra"] [deps.Compat.extensions] @@ -55,7 +57,7 @@ weakdeps = ["Dates", "LinearAlgebra"] [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "1.0.5+0" +version = "1.1.1+0" [[deps.Crayons]] git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15" @@ -63,21 +65,21 @@ uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f" version = "4.1.1" [[deps.DataAPI]] -git-tree-sha1 = "8da84edb865b0b5b0100c0666a9bc9a0b71c553c" +git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" -version = "1.15.0" +version = "1.16.0" [[deps.DataFrames]] -deps = ["Compat", "DataAPI", "DataStructures", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrecompileTools", "PrettyTables", "Printf", "REPL", "Random", "Reexport", "SentinelArrays", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] -git-tree-sha1 = "04c738083f29f86e62c8afc341f0967d8717bdb8" +deps = ["Compat", "DataAPI", "DataStructures", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrecompileTools", "PrettyTables", "Printf", "Random", "Reexport", "SentinelArrays", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] +git-tree-sha1 = "d8928e9169ff76c6281f39a659f9bca3a573f24c" uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" -version = "1.6.1" +version = "1.8.1" [[deps.DataStructures]] deps = ["Compat", "InteractiveUtils", "OrderedCollections"] -git-tree-sha1 = "3dbd312d370723b6bb43ba9d02fc36abade4518d" +git-tree-sha1 = "4e1fe97fdaed23e9dc21d4d664bea76b65fc50a0" uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.18.15" +version = "0.18.22" [[deps.DataValueInterfaces]] git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" @@ -87,16 +89,17 @@ version = "1.0.0" [[deps.Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +version = "1.11.0" [[deps.Distributed]] deps = ["Random", "Serialization", "Sockets"] uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" +version = "1.11.0" [[deps.DocStringExtensions]] -deps = ["LibGit2"] -git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" +git-tree-sha1 = "7442a5dfe1ebb773c29cc2962a8980f47221d76c" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.9.3" +version = "0.9.5" [[deps.Documenter]] deps = ["ANSIColoredPrinters", "Base64", "Dates", "DocStringExtensions", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"] @@ -111,54 +114,75 @@ version = "1.6.0" [[deps.FileIO]] deps = ["Pkg", "Requires", "UUIDs"] -git-tree-sha1 = "299dc33549f68299137e51e6d49a13b5b1da9673" +git-tree-sha1 = "d60eb76f37d7e5a40cc2e7c36974d864b82dc802" uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" -version = "1.16.1" +version = "1.17.1" + + [deps.FileIO.extensions] + HTTPExt = "HTTP" + + [deps.FileIO.weakdeps] + HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" [[deps.FilePathsBase]] -deps = ["Compat", "Dates", "Mmap", "Printf", "Test", "UUIDs"] -git-tree-sha1 = "e27c4ebe80e8699540f2d6c805cc12203b614f12" +deps = ["Compat", "Dates"] +git-tree-sha1 = "3bab2c5aa25e7840a4b065805c0cdfc01f3068d2" uuid = "48062228-2e41-5def-b9a4-89aafe57970f" -version = "0.9.20" +version = "0.9.24" +weakdeps = ["Mmap", "Test"] + + [deps.FilePathsBase.extensions] + FilePathsBaseMmapExt = "Mmap" + FilePathsBaseTestExt = "Test" [[deps.FileWatching]] uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "1.11.0" [[deps.Future]] deps = ["Random"] uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" +version = "1.11.0" [[deps.Graphs]] -deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] -git-tree-sha1 = "1cf1d7dcb4bc32d7b4a5add4232db3750c27ecb4" +deps = ["ArnoldiMethod", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] +git-tree-sha1 = "7a98c6502f4632dbe9fb1973a4244eaa3324e84d" uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" -version = "1.8.0" +version = "1.13.1" [[deps.IOCapture]] deps = ["Logging", "Random"] -git-tree-sha1 = "d75853a0bdbfb1ac815478bacd89cd27b550ace6" +git-tree-sha1 = "b6d6bfdd7ce25b0f9b2f6b3dd56b2673a66c8770" uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" -version = "0.2.3" +version = "0.2.5" [[deps.Inflate]] -git-tree-sha1 = "5cd07aab533df5170988219191dfad0519391428" +git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d" uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" -version = "0.1.3" +version = "0.1.5" [[deps.InlineStrings]] -deps = ["Parsers"] -git-tree-sha1 = "9cc2baf75c6d09f9da536ddf58eb2f29dedaf461" +git-tree-sha1 = "8f3d257792a522b4601c24a577954b0a8cd7334d" uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" -version = "1.4.0" +version = "1.4.5" + + [deps.InlineStrings.extensions] + ArrowTypesExt = "ArrowTypes" + ParsersExt = "Parsers" + + [deps.InlineStrings.weakdeps] + ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" + Parsers = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" [[deps.InteractiveUtils]] deps = ["Markdown"] uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +version = "1.11.0" [[deps.InvertedIndices]] -git-tree-sha1 = "0dc7b50b8d436461be01300fd8cd45aa0274b038" +git-tree-sha1 = "6da3c4316095de0f5ee2ebd875df8721e7e0bdbe" uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" -version = "1.3.0" +version = "1.3.1" [[deps.IteratorInterfaceExtensions]] git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" @@ -166,10 +190,10 @@ uuid = "82899510-4779-5014-852e-03e436cf321d" version = "1.0.0" [[deps.JLD2]] -deps = ["FileIO", "MacroTools", "Mmap", "OrderedCollections", "Pkg", "Printf", "Reexport", "Requires", "TranscodingStreams", "UUIDs"] -git-tree-sha1 = "aa6ffef1fd85657f4999030c52eaeec22a279738" +deps = ["FileIO", "MacroTools", "Mmap", "OrderedCollections", "PrecompileTools", "Requires", "TranscodingStreams"] +git-tree-sha1 = "d2091f3a374453c3873940dc1594417e6a5ebec6" uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819" -version = "0.4.33" +version = "0.4.55" [[deps.JSON]] deps = ["Dates", "Mmap", "Parsers", "Unicode"] @@ -178,53 +202,62 @@ uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" version = "0.21.4" [[deps.LaTeXStrings]] -git-tree-sha1 = "f2355693d6778a178ade15952b7ac47a4ff97996" +git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c" uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" -version = "1.3.0" +version = "1.4.0" [[deps.LibCURL]] deps = ["LibCURL_jll", "MozillaCACerts_jll"] uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" -version = "0.6.3" +version = "0.6.4" [[deps.LibCURL_jll]] deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" -version = "7.84.0+0" +version = "8.6.0+0" [[deps.LibGit2]] -deps = ["Base64", "NetworkOptions", "Printf", "SHA"] +deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"] uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +version = "1.11.0" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.7.2+0" [[deps.LibSSH2_jll]] deps = ["Artifacts", "Libdl", "MbedTLS_jll"] uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" -version = "1.10.2+0" +version = "1.11.0+1" [[deps.Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +version = "1.11.0" [[deps.LinearAlgebra]] deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +version = "1.11.0" [[deps.Logging]] uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" [[deps.MacroTools]] -deps = ["Markdown", "Random"] -git-tree-sha1 = "9ee1618cbf5240e6d4e0371d6f24065083f60c48" +git-tree-sha1 = "1e0228a030642014fe5cfe68c2c0a818f9e3f522" uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" -version = "0.5.11" +version = "0.5.16" [[deps.Markdown]] deps = ["Base64"] uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" [[deps.MbedTLS_jll]] deps = ["Artifacts", "Libdl"] uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.28.2+0" +version = "2.28.6+0" [[deps.MetaGraphs]] deps = ["Graphs", "JLD2", "Random"] @@ -234,16 +267,17 @@ version = "0.7.2" [[deps.Missings]] deps = ["DataAPI"] -git-tree-sha1 = "f66bdc5de519e8f8ae43bdc598782d35a25b1272" +git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" -version = "1.1.0" +version = "1.2.0" [[deps.Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" +version = "1.11.0" [[deps.MozillaCACerts_jll]] uuid = "14a3606d-f60d-562e-9121-12d972cd8159" -version = "2022.10.11" +version = "2023.12.12" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" @@ -252,59 +286,66 @@ version = "1.2.0" [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.21+4" +version = "0.3.27+1" [[deps.OrderedCollections]] -git-tree-sha1 = "2e73fe17cac3c62ad1aebe70d44c963c3cfdc3e3" +git-tree-sha1 = "05868e21324cede2207c6f0f466b4bfef6d5e7ee" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -version = "1.6.2" +version = "1.8.1" [[deps.Parsers]] deps = ["Dates", "PrecompileTools", "UUIDs"] -git-tree-sha1 = "716e24b21538abc91f6205fd1d8363f39b442851" +git-tree-sha1 = "7d2f8f21da5db6a806faf7b9b292296da42b2810" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.7.2" +version = "2.8.3" [[deps.Pkg]] -deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"] uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -version = "1.9.2" +version = "1.11.0" +weakdeps = ["REPL"] + + [deps.Pkg.extensions] + REPLExt = "REPL" [[deps.PooledArrays]] deps = ["DataAPI", "Future"] -git-tree-sha1 = "a6062fe4063cdafe78f4a0a81cfffb89721b30e7" +git-tree-sha1 = "36d8b4b899628fb92c2749eb488d884a926614d3" uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" -version = "1.4.2" +version = "1.4.3" [[deps.PrecompileTools]] deps = ["Preferences"] -git-tree-sha1 = "03b4c25b43cb84cee5c90aa9b5ea0a78fd848d2f" +git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" -version = "1.2.0" +version = "1.2.1" [[deps.Preferences]] deps = ["TOML"] -git-tree-sha1 = "7eb1686b4f04b82f96ed7a4ea5890a4f0c7a09f1" +git-tree-sha1 = "0f27480397253da18fe2c12a4ba4eb9eb208bf3d" uuid = "21216c6a-2e73-6563-6e65-726566657250" -version = "1.4.0" +version = "1.5.0" [[deps.PrettyTables]] -deps = ["Crayons", "LaTeXStrings", "Markdown", "Printf", "Reexport", "StringManipulation", "Tables"] -git-tree-sha1 = "ee094908d720185ddbdc58dbe0c1cbe35453ec7a" +deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "Tables"] +git-tree-sha1 = "6b8e2f0bae3f678811678065c09571c1619da219" uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" -version = "2.2.7" +version = "3.1.0" [[deps.Printf]] deps = ["Unicode"] uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +version = "1.11.0" [[deps.REPL]] -deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +deps = ["InteractiveUtils", "Markdown", "Sockets", "StyledStrings", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +version = "1.11.0" [[deps.Random]] -deps = ["SHA", "Serialization"] +deps = ["SHA"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" [[deps.Reexport]] git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" @@ -313,9 +354,9 @@ version = "1.2.2" [[deps.Requires]] deps = ["UUIDs"] -git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" +git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64" uuid = "ae029012-a4dd-5104-9daa-d747884805df" -version = "1.3.0" +version = "1.3.1" [[deps.SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" @@ -323,66 +364,83 @@ version = "0.7.0" [[deps.SentinelArrays]] deps = ["Dates", "Random"] -git-tree-sha1 = "04bdff0b09c65ff3e06a05e3eb7b120223da3d39" +git-tree-sha1 = "712fb0231ee6f9120e005ccd56297abbc053e7e0" uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" -version = "1.4.0" +version = "1.4.8" [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +version = "1.11.0" [[deps.SharedArrays]] deps = ["Distributed", "Mmap", "Random", "Serialization"] uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" +version = "1.11.0" [[deps.SimpleTraits]] deps = ["InteractiveUtils", "MacroTools"] -git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231" +git-tree-sha1 = "be8eeac05ec97d379347584fa9fe2f5f76795bcb" uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" -version = "0.9.4" +version = "0.9.5" [[deps.Sockets]] uuid = "6462fe0b-24de-5631-8697-dd941f90decc" +version = "1.11.0" [[deps.SortingAlgorithms]] deps = ["DataStructures"] -git-tree-sha1 = "c60ec5c62180f27efea3ba2908480f8055e17cee" +git-tree-sha1 = "64d974c2e6fdf07f8155b5b2ca2ffa9069b608d9" uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" -version = "1.1.1" +version = "1.2.2" [[deps.SparseArrays]] deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.11.0" [[deps.StaticArrays]] -deps = ["LinearAlgebra", "Random", "StaticArraysCore"] -git-tree-sha1 = "51621cca8651d9e334a659443a74ce50a3b6dfab" +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "b8693004b385c842357406e3af647701fe783f98" uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.6.3" -weakdeps = ["Statistics"] +version = "1.9.15" [deps.StaticArrays.extensions] + StaticArraysChainRulesCoreExt = "ChainRulesCore" StaticArraysStatisticsExt = "Statistics" + [deps.StaticArrays.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + [[deps.StaticArraysCore]] -git-tree-sha1 = "36b3d696ce6366023a0ea192b4cd442268995a0d" +git-tree-sha1 = "192954ef1208c7019899fbf8049e717f92959682" uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" -version = "1.4.2" +version = "1.4.3" [[deps.Statistics]] -deps = ["LinearAlgebra", "SparseArrays"] +deps = ["LinearAlgebra"] +git-tree-sha1 = "ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0" uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" -version = "1.9.0" +version = "1.11.1" +weakdeps = ["SparseArrays"] + + [deps.Statistics.extensions] + SparseArraysExt = ["SparseArrays"] [[deps.StringManipulation]] deps = ["PrecompileTools"] -git-tree-sha1 = "a04cabe79c5f01f4d723cc6704070ada0b9d46d5" +git-tree-sha1 = "725421ae8e530ec29bcbdddbe91ff8053421d023" uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e" -version = "0.3.4" +version = "0.4.1" + +[[deps.StyledStrings]] +uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" +version = "1.11.0" [[deps.SuiteSparse_jll]] -deps = ["Artifacts", "Libdl", "Pkg", "libblastrampoline_jll"] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" -version = "5.10.1+6" +version = "7.7.0+0" [[deps.TOML]] deps = ["Dates"] @@ -396,10 +454,10 @@ uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" version = "1.0.1" [[deps.Tables]] -deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "OrderedCollections", "TableTraits", "Test"] -git-tree-sha1 = "1544b926975372da01227b382066ab70e574a3ec" +deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"] +git-tree-sha1 = "f2c1efbc8f3a609aadf318094f8fc5204bdaf344" uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" -version = "1.10.1" +version = "1.12.1" [[deps.Tar]] deps = ["ArgTools", "SHA"] @@ -409,19 +467,21 @@ version = "1.10.0" [[deps.Test]] deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +version = "1.11.0" [[deps.TranscodingStreams]] -deps = ["Random", "Test"] -git-tree-sha1 = "9a6ae7ed916312b41236fcef7e0af564ef934769" +git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742" uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" -version = "0.9.13" +version = "0.11.3" [[deps.UUIDs]] deps = ["Random", "SHA"] uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +version = "1.11.0" [[deps.Unicode]] uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +version = "1.11.0" [[deps.WeakRefStrings]] deps = ["DataAPI", "InlineStrings", "Parsers"] @@ -437,19 +497,19 @@ version = "1.6.1" [[deps.Zlib_jll]] deps = ["Libdl"] uuid = "83775a58-1f1d-513f-b197-d71354ab007a" -version = "1.2.13+0" +version = "1.2.13+1" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.8.0+0" +version = "5.11.0+0" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" -version = "1.48.0+0" +version = "1.59.0+0" [[deps.p7zip_jll]] deps = ["Artifacts", "Libdl"] uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" -version = "17.4.0+0" +version = "17.4.0+2" diff --git a/Project.toml b/Project.toml index 7aae950..8f8b86b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "CurricularAnalytics" uuid = "593ffa3d-269e-5d81-88bc-c3b6809c35a6" authors = ["Greg Heileman ", "Hayden Free "] -version = "1.5.3" +version = "2.0.1" [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" diff --git a/UBW-curric.csv b/UBW-curric.csv new file mode 100644 index 0000000..5c6d2d7 --- /dev/null +++ b/UBW-curric.csv @@ -0,0 +1,13 @@ +Curriculum,Underwater Basket Weaving,,,,,,,,, +Institution,"ACME State University",,,,,,,,, +Degree Type,"BS",,,,,,,,, +System Type,"semester",,,,,,,,, +CIP,"445786",,,,,,,,, +Courses,,,,,,,,,, +Course ID,Course Name,Prefix,Number,Prerequisites,Corequisites,Strict-Corequisites,Credit Hours,Institution,Canonical Name +300052638,"Basket Materials & Decoration","BW","214","4037212392",,,3,"ACME State University","Basket Materials", +1050322132,"Introduction to Baskets","BW","101",,,,3,"ACME State University","Baskets I", +1888571219,"Swimming","PE","115",,,,3,"ACME State University","Physical Education", +2012165139,"Advanced Basketry","CS","300","3989163861",,,3,"ACME State University","Baskets II", +3989163861,"Basic Basket Forms","BW","111","1050322132;1888571219","4037212392",,3,"ACME State University","Baskets I", +4037212392,"Basic Basket Forms Lab","BW","111L",,,,1,"ACME State University","Baskets I Laboratory", \ No newline at end of file diff --git a/src/CSVUtilities.jl b/src/CSVUtilities.jl index f78f204..d4bdf9b 100644 --- a/src/CSVUtilities.jl +++ b/src/CSVUtilities.jl @@ -38,6 +38,7 @@ function find_courses(courses, course_id) return false end +#TODO need to pass in a curriculum in order to access the requisite_clauses array function course_line(course, term_id; metrics=false) course_ID = course.id course_name = course.name @@ -47,7 +48,7 @@ function course_line(course, term_id; metrics=false) course_prereq = "\"" course_coreq = "\"" course_scoreq = "\"" - for requesite in course.requisites + for requesite in course.requisites[1] if requesite[2] == pre course_prereq = course_prereq * string(requesite[1]) * ";" elseif requesite[2] == co diff --git a/src/CurricularAnalytics.jl b/src/CurricularAnalytics.jl index 1aa5428..2bbee93 100644 --- a/src/CurricularAnalytics.jl +++ b/src/CurricularAnalytics.jl @@ -28,7 +28,7 @@ include("RequirementsAnalytics.jl") export AA, AAS, AS, AbstractCourse, AbstractRequirement, BA, BS, Course, CourseCollection, CourseCatalog, CourseRecord, CourseSet, credit_balance, Curriculum, DegreePlan, EdgeClass, Enrollment, Grade, LearningOutcome, PassRate, RequirementSet, Requisite, Student, StudentRecord, Simulation, System, Term, TransferArticulation, - add_course!, add_lo_requisite!, add_requisite!, add_transfer_catalog, add_transfer_course, all_paths, back_edge, basic_metrics, basic_statistics, + add_course!, add_lo_requisite!, add_requisite!, add_requisite_clause!, add_transfer_catalog, add_transfer_course, all_paths, back_edge, basic_metrics, basic_statistics, bin_filling, blocking_factor, centrality, co, compare_curricula, convert_ids, complexity, course, course_from_id, course_from_vertex, course_id, courses_from_vertices, create_degree_plan, cross_edge, dead_ends, delay_factor, delete_requisite!, dfs, extraneous_requisites, find_term, forward_edge, gad, grade, homology, is_duplicate, is_valid, isvalid_curriculum, isvalid_degree_plan, level, longest_path, longest_paths, merge_curricula, pass_table, passrate_table, @@ -77,7 +77,7 @@ function extraneous_requisites(c::Curriculum; print=false) remove = true for n in nb # check for co- or strict_co requisites if has_path(c.graph, n, v) # is there a path from n to v? - req_type = c.courses[n].requisites[c.courses[u].id] # the requisite relationship between u and n + req_type = c.courses[n].requisites[c.requisite_clauses[c.courses[n].id]][c.courses[u].id] # the requisite relationship between u and n if (req_type == co) || (req_type == strict_co) # is u a co or strict_co requisite for n? remove = false # a co or strict_co relationshipo is involved, must keep (u, v) end @@ -629,6 +629,7 @@ courses must be identical (at the level of memory allocation). Allowable match c - `credit hours` : the course credit hours must be indentical. """ +#TODO Integrate use of requisite_clauses into this function function merge_curricula(name::AbstractString, c1::Curriculum, c2::Curriculum, match_criteria::Array{String}=Array{String,1}(); learning_outcomes::Array{LearningOutcome}=Array{LearningOutcome,1}(), degree_type::AbstractString=BS, system_type::System=semester, institution::AbstractString="", CIP::AbstractString="") @@ -654,19 +655,19 @@ function merge_curricula(name::AbstractString, c1::Curriculum, c2::Curriculum, m for (j,c) in enumerate(extra_courses) # print("\n $(c.name): ") # print("total requisistes = $(length(c.requisites)),") - for req in keys(c.requisites) + for req in keys(c.requisites[1]) # print(" requisite id: $(req) ") req_course = course_from_id(c2, req) if find_match(req_course, merged_courses, match_criteria) != nothing # requisite already exists in c1 # print(" match in c1 - $(course_from_id(c1, req).name) ") - add_requisite!(req_course, new_courses[j], c.requisites[req]) + add_requisite!(req_course, new_courses[j], c.requisites[1][req]) elseif find_match(req_course, extra_courses, match_criteria) != nothing # requisite is not in c1, but it's in c2 -- use the id of the new course created for it # print(" match in extra courses, ") i = findfirst(x->x==req_course, extra_courses) # print(" index of match = $i ") - add_requisite!(new_courses[i], new_courses[j], c.requisites[req]) + add_requisite!(new_courses[i], new_courses[j], c.requisites[1][req]) else # requisite is neither in c1 or 2 -- this shouldn't happen => error error("requisite error on course: $(c.name)") end diff --git a/src/DataTypes/Course.jl b/src/DataTypes/Course.jl index 5a34738..92e9a04 100644 --- a/src/DataTypes/Course.jl +++ b/src/DataTypes/Course.jl @@ -56,7 +56,7 @@ mutable struct Course <: AbstractCourse cross_listed::Array{Course} # courses that are cross-listed with the course (same as "also offered as") canonical_name::AbstractString # Standard name used to denote the course in the # discipline, e.g., Psychology I - requisites::Dict{Int, Requisite} # List of requisites, in (requisite_course id, requisite_type) format + requisites::Array{Dict{Int, Requisite}, 1} # Array of requisite clauses, each clause in (requisite_course id, requisite_type) format is assumed to be clause in a DNF formula. learning_outcomes::Array{LearningOutcome} # A list of learning outcomes associated with the course metrics::Dict{String, Any} # Course-related metrics metadata::Dict{String, Any} # Course-related metadata @@ -65,7 +65,7 @@ mutable struct Course <: AbstractCourse # Constructor function Course(name::AbstractString, credit_hours::Real; prefix::AbstractString="", learning_outcomes::Array{LearningOutcome}=Array{LearningOutcome,1}(), - num::AbstractString="", institution::AbstractString="", college::AbstractString="", department::AbstractString="", + num::AbstractString="", institution::AbstractString="", college::AbstractString="", department::AbstractString="", requisites::Array{Dict{Int, Requisite}, 1}=fill(Dict{Int, Requisite}(), 1), cross_listed::Array{Course}=Array{Course,1}(), canonical_name::AbstractString="", id::Int=0, passrate::Float64=0.5) this = new() this.name = name @@ -82,14 +82,12 @@ mutable struct Course <: AbstractCourse this.department = department this.cross_listed = cross_listed this.canonical_name = canonical_name - this.requisites = Dict{Int, Requisite}() - #this.requisite_formula + this.requisites = requisites # create an empty first DNF clause for requisites this.metrics = Dict{String, Any}() this.metadata = Dict{String, Any}() this.learning_outcomes = learning_outcomes this.vertex_id = Dict{Int, Int}() # curriculum id -> vertex id, note: course may be in multiple curricula - this.passrate = passrate return this end @@ -106,7 +104,7 @@ mutable struct CourseCollection <: AbstractCourse college::AbstractString # College or school (within the institution) offering the course department::AbstractString # Department (within the school or college) offering the course canonical_name::AbstractString # Standard name used to denote the course collection, e.g., math genearl education - requisites::Dict{Int, Requisite} # List of requisites, in (requisite_course id, requisite_type) format + requisites::Array{Dict{Int, Requisite}, 1} # Array of requisite clauses, each clause in (requisite_course id, requisite_type) format metrics::Dict{String, Any} # Course-related metrics metadata::Dict{String, Any} # Course-related metadata @@ -126,8 +124,7 @@ mutable struct CourseCollection <: AbstractCourse this.college = college this.department = department this.canonical_name = canonical_name - this.requisites = Dict{Int, Requisite}() - #this.requisite_formula + this.requisites = fill(Dict{Int, Requisite}(), 1) # create an empty first DNF clause for requisites this.metrics = Dict{String, Any}() this.metadata = Dict{String, Any}() this.vertex_id = Dict{Int, Int}() # curriculum id -> vertex id @@ -135,6 +132,7 @@ mutable struct CourseCollection <: AbstractCourse end end +# create a unique course id for a couse by hashing on the course's prefix, num, name, institution. function course_id(prefix::AbstractString, num::AbstractString, name::AbstractString, institution::AbstractString) convert(Int, mod(hash(name * prefix * num * institution), UInt32)) end @@ -150,14 +148,32 @@ Required: - `tc::AbstractCourse` : target course, i.e., course for which `rc` is a requisite. - `requisite_type::Requisite` : requisite type. +# Keyword Arguments +- `clause::Int` : specify the DNF clause the requisite should appear in. + + # Requisite types One of the following requisite types must be specified for the `requisite_type`: - `pre` : a prerequisite course that must be passed before `tc` can be attempted. - `co` : a co-requisite course that may be taken before or at the same time as `tc`. - `strict_co` : a strict co-requisite course that must be taken at the same time as `tc`. """ -function add_requisite!(requisite_course::AbstractCourse, course::AbstractCourse, requisite_type::Requisite) - course.requisites[requisite_course.id] = requisite_type +function add_requisite!(requisite_course::AbstractCourse, course::AbstractCourse, requisite_type::Requisite; clause::Int=1) + if clause > length(course.requisites) && clause == length(course.requisites) + 1 + # We allow the caluse to be 1 greater than the length of the requisites array, and we will add the clause + add_requisite_clause!(course) + elseif clause > length(course.requisites) + error( + """ + Clause number cannot exceed the number of requisite clauses that exist on the course by more than one. + This means you can only add clauses in order. (clause = 1, 2, 3, ...) + It should never occur, but should you need to build clauses out of order you can use the add_requisite_clause! function to pad the requisite array with empty clauses. + """ + ) + elseif clause < 1 + error("Clause number must be greater than or equal to 1") + end + course.requisites[clause][requisite_course.id] = requisite_type end """ @@ -171,21 +187,42 @@ Required: - `tc::AbstractCourse` : target course, i.e., course for which `rc` is a requisite. - `requisite_type::Array{Requisite}` : an array of requisite types. +# Keyword Arguments +- `clause::Int` : specify the DNF clause the requisites should appear in (default = 1). + # Requisite types The following requisite types may be specified for the `requisite_type`: - `pre` : a prerequisite course that must be passed before `tc` can be attempted. - `co` : a co-requisite course that may be taken before or at the same time as `tc`. - `strict_co` : a strict co-requisite course that must be taken at the same time as `tc`. """ -function add_requisite!(requisite_courses::Array{AbstractCourse}, course::AbstractCourse, requisite_types::Array{Requisite}) +function add_requisite!(requisite_courses::Array{<:AbstractCourse}, course::AbstractCourse, requisite_types::Array{Requisite}; clause::Int=1) + # We don't want someone to create clauses 1, 3, 5, and 7 + # You should only be able to create clauses in order (1, 2, 3, 4, ...) + # Previously, we were asserting that the clause was less than or equal to the length of the requisites array (prevent out of index errors) + # @assert clause <= length(course.requisites) + if clause > length(course.requisites) && clause == length(course.requisites) + 1 + # We allow the caluse to be 1 greater than the length of the requisites array, and we will add the clause + add_requisite_clause!(course) + elseif clause > length(course.requisites) + error( + """ + Clause number cannot exceed the number of requisite clauses that exist on the course by more than one. + This means you can only add clauses in order. (clause = 1, 2, 3, ...) + It should never occur, but should you need to build clauses out of order you can use the add_requisite_clause! function to pad the requisite array with empty clauses. + """ + ) + elseif clause < 1 + error("Clause number must be greater than or equal to 1") + end @assert length(requisite_courses) == length(requisite_types) for i = 1:length(requisite_courses) - course.requisites[requisite_courses[i].id] = requisite_types[i] + course.requisites[clause][requisite_courses[i].id] = requisite_types[i] end end """ - delete_requisite!(rc, tc) + delete_requisite!(rc, tc, clause) Remove course rc as a requisite for target course tc. If rc is not an existing requisite for tc, an error is thrown. @@ -195,10 +232,32 @@ Required: - `rc::AbstractCourse` : requisite course. - `tc::AbstractCourse` : target course, i.e., course for which `rc` is a requisite. +# Keyword Arguments +- `clause::Int` : the DNF clause the requisite should be deleted from (default = 1). + """ -function delete_requisite!(requisite_course::Course, course::Course) +function delete_requisite!(requisite_course::Course, course::Course; clause::Int=1) #if !haskey(course.requisites, requisite_course.id) # error("The requisite you are trying to delete does not exist") #end - delete!(course.requisites, requisite_course.id) + @assert clause <= length(course.requisites) + delete!(course.requisites[clause], requisite_course.id) end + +""" + add_requisite_clause!(course::AbstractCourse) + +Add an empty clause to course's requisite formula. The clauses are stored in an array representing the clauses in a DNF expression. +The clause number (i.e., index) in the requisite array is returned. + +# Arguments +Required: +- `c::AbstractCourse` : target course. + +""" +function add_requisite_clause!(course::AbstractCourse) + return length(push!(course.requisites, Dict{Int, Requisite}())) +end + +# TODO: Create a version of add_requisite_clasue! that has an initial set of requisites (similar to add_requisite!) +# TODO: Add a delete_requisite_clause function. Issue: how do you specify the clause, by index, or some other search method? diff --git a/src/DataTypes/Curriculum.jl b/src/DataTypes/Curriculum.jl index 395c7a7..91f8298 100644 --- a/src/DataTypes/Curriculum.jl +++ b/src/DataTypes/Curriculum.jl @@ -29,24 +29,24 @@ mutable struct Curriculum id::Int # Unique curriculum ID name::AbstractString # Name of the curriculum (can be used as an identifier) institution::AbstractString # Institution offering the curriculum - degree_type::AbstractString # Type of degree_type + degree_type::AbstractString # Type of degree_type system_type::System # Semester or quarter system CIP::AbstractString # CIP code associated with the curriculum - courses::Array{AbstractCourse} # Array of required courses in curriculum + courses::Array{AbstractCourse} # Array of required courses in curriculum + requisite_clauses::Dict{Int, Int} # Dictionary of the requisite clause that should be applied to each course in the curriculum, in (course_id, clause #) format num_courses::Int # Number of required courses in curriculum credit_hours::Real # Total number of credit hours in required curriculum graph::SimpleDiGraph{Int} # Directed graph representation of pre-/co-requisite structure # of the curriculum, note: this is a course graph learning_outcomes::Array{LearningOutcome} # A list of learning outcomes associated with the curriculum - learning_outcome_graph::SimpleDiGraph{Int} # Directed graph representatin of pre-/co-requisite structure of learning - # outcomes in the curriculum + learning_outcome_graph::SimpleDiGraph{Int} # Directed graph representatin of pre-/co-requisite structure of learning outcomes in the curriculum course_learning_outcome_graph::MetaDiGraph{Int} # Directed Int64 metagraph with Float64 weights defined by :weight (default weight 1.0) # This is a course and learning outcome graph metrics::Dict{String, Any} # Curriculum-related metrics metadata::Dict{String, Any} # Curriculum-related metadata # Constructor - function Curriculum(name::AbstractString, courses::Array{AbstractCourse}; learning_outcomes::Array{LearningOutcome}=Array{LearningOutcome,1}(), + function Curriculum(name::AbstractString, courses::Array{AbstractCourse}; requisite_clauses::Dict{Int, Int}=Dict{Int, Int}(), learning_outcomes::Array{LearningOutcome}=Array{LearningOutcome,1}(), degree_type::AbstractString="BS", system_type::System=semester, institution::AbstractString="", CIP::AbstractString="", id::Int=0, sortby_ID::Bool=true) this = new() @@ -67,15 +67,23 @@ mutable struct Curriculum end this.num_courses = length(this.courses) this.credit_hours = total_credits(this) - this.graph = SimpleDiGraph{Int}() - create_graph!(this) this.metrics = Dict{String, Any}() this.metadata = Dict{String, Any}() + this.requisite_clauses = requisite_clauses + for c ∈ this.courses + if c.id ∉ keys(this.requisite_clauses) + this.requisite_clauses[c.id] = 1 # if a requisite clause is not specificed for a course, set it to 1 + else # a clause number was specified for a course in a curriculum, make sure are at least that many requisite clauses in the course + @assert requisite_clauses[c.id] <= length(c.requisites) + end + end + this.graph = SimpleDiGraph{Int}() + create_graph!(this) this.learning_outcomes = learning_outcomes this.learning_outcome_graph = SimpleDiGraph{Int}() - create_learning_outcome_graph!(this) + #create_learning_outcome_graph!(this) this.course_learning_outcome_graph = MetaDiGraph() - create_course_learning_outcome_graph!(this) + #create_course_learning_outcome_graph!(this) errors = IOBuffer() if !(is_valid(this, errors)) printstyled("WARNING: Curriculum was created, but is invalid due to requisite cycle(s):", color = :yellow) @@ -84,11 +92,11 @@ mutable struct Curriculum return this end - function Curriculum(name::AbstractString, courses::Array{Course}; learning_outcomes::Array{LearningOutcome}=Array{LearningOutcome,1}(), - degree_type::AbstractString="BS", system_type::System=semester, institution::AbstractString="", CIP::AbstractString="", - id::Int=0, sortby_ID::Bool=true) - Curriculum(name, convert(Array{AbstractCourse},courses), learning_outcomes=learning_outcomes, degree_type=degree_type, - system_type=system_type, institution=institution, CIP=CIP, id=id, sortby_ID=sortby_ID) + function Curriculum(name::AbstractString, courses::Array{Course}; requisite_clauses::Dict{Int, Int}=Dict{Int, Int}(), + learning_outcomes::Array{LearningOutcome}=Array{LearningOutcome,1}(), degree_type::AbstractString="BS", system_type::System=semester, + institution::AbstractString="", CIP::AbstractString="", id::Int=0, sortby_ID::Bool=true) + Curriculum(name, convert(Array{AbstractCourse},courses), requisite_clauses=requisite_clauses, learning_outcomes=learning_outcomes, + degree_type=degree_type, system_type=system_type, institution=institution, CIP=CIP, id=id, sortby_ID=sortby_ID) end end @@ -124,7 +132,7 @@ function is_valid(c::Curriculum, error_msg::IOBuffer=IOBuffer()) # the opposite direction. If this creates any cycles of length greater than 2 in the modified graph (i.e., involving # more than the two courses in the strict-corequisite relationship), then the curriculum is unsatisfiable. for course in c.courses - for (k,r) in course.requisites + for (k,r) in course.requisites[c.requisite_clauses[course.id]] if r == strict_co v_d = course_from_id(c,course.id).vertex_id[c.id] # destination vertex v_s = course_from_id(c,k).vertex_id[c.id] # source vertex @@ -176,10 +184,12 @@ function convert_ids(curriculum::Curriculum) old_id = c1.id c1.id = mod(hash(c1.name * c1.prefix * c1.num * c1.institution), UInt32) if old_id != c1.id - for c2 in curriculum.courses - if old_id in keys(c2.requisites) - add_requisite!(c1, c2, c2.requisites[old_id]) - delete!(c2.requisites, old_id) + for c2 in curriculum.courses # must change the id in all of the requisites + for (i, clause) in enumerate(c2.requisites) # check each clause in the DNF, even if it's not being used in the curriculum + if old_id in keys(clause) + add_requisite!(c1, c2, clause[old_id], clause=i) + delete!(clause, old_id) #TODO: why are we not using delete_requisite!() + end end end end @@ -265,11 +275,15 @@ function create_graph!(curriculum::Curriculum) end mapped_vertex_ids = map_vertex_ids(curriculum) for c in curriculum.courses - for r in collect(keys(c.requisites)) - if add_edge!(curriculum.graph, mapped_vertex_ids[r], c.vertex_id[curriculum.id]) - else - s = course_from_id(curriculum, r) - error("edge could not be created: ($(s.name), $(c.name))") + for r in collect(keys(c.requisites[curriculum.requisite_clauses[c.id]])) + if r ∉ keys(mapped_vertex_ids) + printstyled("WARNING: A requisite for course $(c.name) is not in curriculum $(curriculum.name)\n", color = :yellow) + else + if add_edge!(curriculum.graph, mapped_vertex_ids[r], c.vertex_id[curriculum.id]) + else + s = course_from_id(curriculum, r) + error("edge could not be created: ($(s.name), $(c.name))") + end end end end @@ -278,8 +292,8 @@ end #""" # create_course_learning_outcome_graph!(c::Curriculum) # -#Create a curriculum directed graph from a curriculum specification. This graph graph contains courses and learning outcomes -# of the curriculum. The graph is stored as a LightGraph.jl implemenation within the Curriculum data object. +#Create a curriculum directed graph from a curriculum specification. This graph contains courses and learning outcomes +# of the curriculum. The graph is stored as a Graph.jl implemenation within the Curriculum data object. #""" @@ -297,7 +311,6 @@ function create_course_learning_outcome_graph!(curriculum::Curriculum) end end - for (j, lo) in enumerate(curriculum.learning_outcomes) if add_vertex!(curriculum.course_learning_outcome_graph) lo.vertex_id[curriculum.id] = len_courses + j # The vertex id of a learning outcome w/in the curriculum @@ -307,16 +320,13 @@ function create_course_learning_outcome_graph!(curriculum::Curriculum) error("vertex could not be created") end end - mapped_vertex_ids = map_vertex_ids(curriculum) mapped_lo_vertex_ids = map_lo_vertex_ids(curriculum) - - # Add edges among courses for c in curriculum.courses - for r in collect(keys(c.requisites)) + for r in collect(keys(c.requisites[curriculum.requisite_clauses[c.id]])) if add_edge!(curriculum.course_learning_outcome_graph, mapped_vertex_ids[r], c.vertex_id[curriculum.id]) - set_prop!(curriculum.course_learning_outcome_graph, Edge(mapped_vertex_ids[r], c.vertex_id[curriculum.id]), :c_to_c, c.requisites[r]) + set_prop!(curriculum.course_learning_outcome_graph, Edge(mapped_vertex_ids[r], c.vertex_id[curriculum.id]), :c_to_c, c.requisites[curriculum.requisite_clauses[c.id]][r]) else s = course_from_id(curriculum, r) @@ -324,7 +334,6 @@ function create_course_learning_outcome_graph!(curriculum::Curriculum) end end end - # Add edges among learning_outcomes for lo in curriculum.learning_outcomes for r in collect(keys(lo.requisites)) @@ -336,7 +345,6 @@ function create_course_learning_outcome_graph!(curriculum::Curriculum) end end end - # Add edges between each pair of a course and a learning outcome for c in curriculum.courses for lo in c.learning_outcomes @@ -388,9 +396,9 @@ function requisite_type(curriculum::Curriculum, src_course_id::Int, dst_course_i dst = c end end - if ((src == 0 || dst == 0) || !haskey(dst.requisites, src.id)) + if ((src == 0 || dst == 0) || !haskey(dst.requisites[curriculum.requisite_clauses[dst.id]], src.id)) error("edge ($src_course_id, $dst_course_id) does not exist in curriculum graph") else - return dst.requisites[src.id] + return dst.requisites[curriculum.requisite_clauses[dst.id]][src.id] end end \ No newline at end of file diff --git a/src/DataTypes/DegreePlan.jl b/src/DataTypes/DegreePlan.jl index cd5536e..f885a31 100644 --- a/src/DataTypes/DegreePlan.jl +++ b/src/DataTypes/DegreePlan.jl @@ -127,7 +127,7 @@ function is_valid(plan::DegreePlan, error_msg::IOBuffer=IOBuffer()) for c in plan.terms[i].courses for j in i-1:-1:1 for k in plan.terms[j].courses - for l in keys(k.requisites) + for l in keys(k.requisites[plan.curriculum.requisite_clauses[k.id]]) if l == c.id validity = false write(error_msg, "\n-Invalid requisite: $(c.name) in term $i is a requisite for $(k.name) in term $j") @@ -143,8 +143,8 @@ function is_valid(plan::DegreePlan, error_msg::IOBuffer=IOBuffer()) for r in plan.terms[i].courses if c == r continue - elseif haskey(c.requisites, r.id) - if c.requisites[r.id] == pre + elseif haskey(c.requisites[plan.curriculum.requisite_clauses[c.id]], r.id) + if c.requisites[plan.curriculum.requisite_clauses[c.id]][r.id] == pre validity = false write(error_msg, "\n-Invalid prerequisite: $(r.name) in term $i is a prerequisite for $(c.name) in the same term") end diff --git a/src/DegreePlanAnalytics.jl b/src/DegreePlanAnalytics.jl index d7c710e..f7422bf 100644 --- a/src/DegreePlanAnalytics.jl +++ b/src/DegreePlanAnalytics.jl @@ -102,7 +102,7 @@ The requisite distance metric computed by this function is stored in the associa function requisite_distance(plan::DegreePlan, course::Course) distance = 0 term = find_term(plan, course) - for req in keys(course.requisites) + for req in keys(course.requisites[plan.curriculum.requisite_clauses[course.id]]) distance = distance + (term - find_term(plan, course_from_id(plan.curriculum, req))) end return course.metrics["requisite distance"] = distance diff --git a/src/DegreePlanCreation.jl b/src/DegreePlanCreation.jl index 94dbc7e..11e84b2 100644 --- a/src/DegreePlanCreation.jl +++ b/src/DegreePlanCreation.jl @@ -15,7 +15,7 @@ function bin_filling(curric::Curriculum, additional_courses::Array{AbstractCours terms = Array{Term,1}() term_credits = 0 term_courses = Array{AbstractCourse,1}() - UC = sort!(deepcopy(curric.courses), by=course_num) # lower numbered courses will be considered first + UC = sort!(deepcopy(curric.courses), by=course_num) # course_num is a function, lower numbered courses will be considered first while length(UC) > 0 if ((c = select_vertex(curric, term_courses, UC)) != nothing) deleteat!(UC, findfirst(isequal(c), UC)) @@ -29,7 +29,7 @@ function bin_filling(curric::Curriculum, additional_courses::Array{AbstractCours end # if c serves as a strict-corequisite for other courses, include them in current term too for course in UC - for req in course.requisites + for req in course.requisites[curric.requisite_clauses[course.id]] if req[1] == c.id if req[2] == strict_co deleteat!(UC, findfirst(isequal(course), UC)) @@ -66,7 +66,7 @@ function select_vertex(curric::Curriculum, term_courses::Array{AbstractCourse,1} if invariant1 == true invariant2 = true for c in term_courses - if c.id in collect(keys(target.requisites)) && target.requisites[c.id] == pre # AND shortcircuits, otherwise 2nd expression would error + if c.id in collect(keys(target.requisites[curric.requisite_clauses[target.id]])) && target.requisites[curric.requisite_clauses[target.id]][c.id] == pre # AND shortcircuits, otherwise 2nd expression would error invariant2 = false break # try a new target end diff --git a/src/RequirementsAnalytics.jl b/src/RequirementsAnalytics.jl index 9d73ad6..2acfb36 100644 --- a/src/RequirementsAnalytics.jl +++ b/src/RequirementsAnalytics.jl @@ -102,9 +102,8 @@ status of each degree requirement. Note: the `satisfied` function can be used t ```julia-repl julia> model = assign_courses(transcript, program_requirements, applied_credits) julia> x = model.obj_dict[:x] -julia> is_satisfied = Dict{Int,Tuple{Int,Array{Int,1}}}() -julia> satisfied(program_requirements, coalesce_transcript(transcript), flatten_requirements(program_requirements), value.(x), is_satisfied) -julia> show_requirement(program_requirements, satisfied=is_satisfied) +julia> is_satisfied = satisfied(coalesce_transcript(transcript), flatten_requirements(program_requirements), value.(x), is_satisfied) +julia> show_requirements(program_requirements, satisfied=is_satisfied) ```` """ function show_requirements(root::AbstractRequirement; io::IO = stdout, tab = " ", diff --git a/src/Simulation/Enrollment.jl b/src/Simulation/Enrollment.jl index d4074b3..87fddb9 100644 --- a/src/Simulation/Enrollment.jl +++ b/src/Simulation/Enrollment.jl @@ -16,7 +16,7 @@ module Enrollment for student in simulation.enrolled_students # Get the coreqs of the the course - strict_coreq_ids = [k for (k, v) in course.requisites if v == strict_co] + strict_coreq_ids = [k for (k, v) in course.requisites[1] if v == strict_co] # Enroll in strictCoreqs first for strict_coreq_id in strict_coreq_ids @@ -36,7 +36,7 @@ module Enrollment end # Get the coreqs of the the course - coreq_ids = [k for (k, v) in course.requisites if v == co] + coreq_ids = [k for (k, v) in course.requisites[1] if v == co] # Enroll in coreqs for coreqId in coreq_ids @@ -55,8 +55,6 @@ module Enrollment end end - - # Determine wheter the student can be enrolled in the current course. if canEnroll(student, course, courses, student_progress, max_credits, current_term) @@ -132,7 +130,7 @@ module Enrollment reqs = Course[] req_ids = [] - req_ids = [k for (k, v) in target_course.requisites if v == req] + req_ids = [k for (k, v) in target_course.requisites[1] if v == req] for req_id in req_ids course = get_course_by_id(courses, req_id) diff --git a/test/CurricularAnalytics.jl b/test/CurricularAnalytics.jl index 0ec4436..74a888a 100644 --- a/test/CurricularAnalytics.jl +++ b/test/CurricularAnalytics.jl @@ -147,6 +147,12 @@ errors = IOBuffer() @test is_valid(curric, errors) == true @test length(extraneous_requisites(curric)) == 0 +# Test case where the requisite for a course in a curriculum is not itself in the curriculum +H = Course("H", 3) +add_requisite!(H,G,pre) # H is a prerequiste to G, but not in the curriculum +curric = Curriculum("Postmodern Basket Weaving", [A,B,C,D,E,F,G], sortby_ID=false) +# this should issue a warning + # Test analytics @test delay_factor(curric) == (33.0, [5.0, 5.0, 5.0, 5.0, 3.0, 5.0, 5.0]) @test blocking_factor(curric) == (16, [6, 3, 4, 2, 0, 0, 1]) diff --git a/test/DataHandler.jl b/test/DataHandler.jl index ff2fc07..7288eea 100644 --- a/test/DataHandler.jl +++ b/test/DataHandler.jl @@ -22,7 +22,7 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].num == "110" @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Baskets I" - @test length(curric.courses[i].requisites) == 0 + @test length(curric.courses[i].requisites[1]) == 0 elseif c.id == 2 @test curric.courses[i].name == "Swimming" @test curric.courses[i].credit_hours == 3 @@ -30,7 +30,7 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].num == "115" @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Physical Education" - @test length(curric.courses[i].requisites) == 0 + @test length(curric.courses[i].requisites[1]) == 0 elseif c.id == 3 @test curric.courses[i].name == "Introductory Calculus w/ Basketry Applications" @test curric.courses[i].credit_hours == 4 @@ -38,7 +38,7 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].num == "116" @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Calculus I" - @test length(curric.courses[i].requisites) == 0 + @test length(curric.courses[i].requisites[1]) == 0 elseif c.id == 4 @test curric.courses[i].name == "Basic Basket Forms" @test curric.courses[i].credit_hours == 3 @@ -46,8 +46,8 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].num == "111" @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Baskets II" - @test curric.courses[i].requisites[1] == pre - @test curric.courses[i].requisites[5] == strict_co + @test curric.courses[i].requisites[1][1] == pre + @test curric.courses[i].requisites[1][5] == strict_co elseif c.id == 5 @test curric.courses[i].name == "Basic Basket Forms Lab" @test curric.courses[i].credit_hours == 1 @@ -55,7 +55,7 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].num == "111L" @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Baskets II Laboratory" - @test length(curric.courses[i].requisites) == 0 + @test length(curric.courses[i].requisites[1]) == 0 elseif c.id == 6 @test curric.courses[i].name == "Advanced Basketry" @test curric.courses[i].credit_hours == 3 @@ -63,9 +63,9 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].num == "201" @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Baskets III" - @test curric.courses[i].requisites[4] == pre - @test curric.courses[i].requisites[5] == pre - @test curric.courses[i].requisites[3] == co + @test curric.courses[i].requisites[1][4] == pre + @test curric.courses[i].requisites[1][5] == pre + @test curric.courses[i].requisites[1][3] == co elseif c.id == 7 @test curric.courses[i].name == "Basket Materials & Decoration" @test curric.courses[i].credit_hours == 3 @@ -73,7 +73,7 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].num == "214" @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Basket Materials" - @test curric.courses[i].requisites[1] == pre + @test curric.courses[i].requisites[1][1] == pre elseif c.id == 8 @test curric.courses[i].name == "Underwater Weaving" @test curric.courses[i].credit_hours == 3 @@ -81,28 +81,28 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].num == "301" @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Baskets IV" - @test curric.courses[i].requisites[2] == pre - @test curric.courses[i].requisites[7] == co + @test curric.courses[i].requisites[1][2] == pre + @test curric.courses[i].requisites[1][7] == co elseif c.id == 9 @test curric.courses[i].name == "Humanitites Elective" @test curric.courses[i].credit_hours == 3 @test curric.courses[i].institution == "ACME State University" - @test length(curric.courses[i].requisites) == 0 + @test length(curric.courses[i].requisites[1]) == 0 elseif c.id == 10 @test curric.courses[i].name == "Social Sciences Elective" @test curric.courses[i].credit_hours == 3 @test curric.courses[i].institution == "ACME State University" - @test length(curric.courses[i].requisites) == 0 + @test length(curric.courses[i].requisites[1]) == 0 elseif c.id == 11 @test curric.courses[i].name == "Technical Elective" @test curric.courses[i].credit_hours == 3 @test curric.courses[i].institution == "ACME State University" - @test length(curric.courses[i].requisites) == 0 + @test length(curric.courses[i].requisites[1]) == 0 elseif c.id == 12 @test curric.courses[i].name == "General Elective" @test curric.courses[i].credit_hours == 3 @test curric.courses[i].institution == "ACME State University" - @test length(curric.courses[i].requisites) == 0 + @test length(curric.courses[i].requisites[1]) == 0 end end # TODO: add learning outcomes @@ -132,7 +132,7 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].num == "110" @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Baskets I" - @test length(curric.courses[i].requisites) == 0 + @test length(curric.courses[i].requisites[1]) == 0 elseif c.id == 2 @test curric.courses[i].name == "Swimming" @test curric.courses[i].credit_hours == 3 @@ -140,7 +140,7 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].num == "115" @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Physical Education" - @test length(curric.courses[i].requisites) == 0 + @test length(curric.courses[i].requisites[1]) == 0 elseif c.id == 3 @test curric.courses[i].name == "Introductory Calculus w/ Basketry Applications" @test curric.courses[i].credit_hours == 4 @@ -149,7 +149,7 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Calculus I" # this is the only difference from above tests - @test curric.courses[i].requisites[13] == pre + @test curric.courses[i].requisites[1][13] == pre elseif c.id == 4 @test curric.courses[i].name == "Basic Basket Forms" @test curric.courses[i].credit_hours == 3 @@ -157,8 +157,8 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].num == "111" @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Baskets II" - @test curric.courses[i].requisites[1] == pre - @test curric.courses[i].requisites[5] == strict_co + @test curric.courses[i].requisites[1][1] == pre + @test curric.courses[i].requisites[1][5] == strict_co elseif c.id == 5 @test curric.courses[i].name == "Basic Basket Forms Lab" @test curric.courses[i].credit_hours == 1 @@ -166,7 +166,7 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].num == "111L" @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Baskets II Laboratory" - @test length(curric.courses[i].requisites) == 0 + @test length(curric.courses[i].requisites[1]) == 0 elseif c.id == 6 @test curric.courses[i].name == "Advanced Basketry" @test curric.courses[i].credit_hours == 3 @@ -174,9 +174,9 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].num == "201" @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Baskets III" - @test curric.courses[i].requisites[4] == pre - @test curric.courses[i].requisites[5] == pre - @test curric.courses[i].requisites[3] == co + @test curric.courses[i].requisites[1][4] == pre + @test curric.courses[i].requisites[1][5] == pre + @test curric.courses[i].requisites[1][3] == co elseif c.id == 7 @test curric.courses[i].name == "Basket Materials & Decoration" @test curric.courses[i].credit_hours == 3 @@ -184,7 +184,7 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].num == "214" @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Basket Materials" - @test curric.courses[i].requisites[1] == pre + @test curric.courses[i].requisites[1][1] == pre elseif c.id == 8 @test curric.courses[i].name == "Underwater Weaving" @test curric.courses[i].credit_hours == 3 @@ -192,28 +192,28 @@ for (i,c) in enumerate(curric.courses) @test curric.courses[i].num == "301" @test curric.courses[i].institution == "ACME State University" @test curric.courses[i].canonical_name == "Baskets IV" - @test curric.courses[i].requisites[2] == pre - @test curric.courses[i].requisites[7] == co + @test curric.courses[i].requisites[1][2] == pre + @test curric.courses[i].requisites[1][7] == co elseif c.id == 9 @test curric.courses[i].name == "Humanitites Elective" @test curric.courses[i].credit_hours == 3 @test curric.courses[i].institution == "ACME State University" - @test length(curric.courses[i].requisites) == 0 + @test length(curric.courses[i].requisites[1]) == 0 elseif c.id == 10 @test curric.courses[i].name == "Social Sciences Elective" @test curric.courses[i].credit_hours == 3 @test curric.courses[i].institution == "ACME State University" - @test length(curric.courses[i].requisites) == 0 + @test length(curric.courses[i].requisites[1]) == 0 elseif c.id == 11 @test curric.courses[i].name == "Technical Elective" @test curric.courses[i].credit_hours == 3 @test curric.courses[i].institution == "ACME State University" - @test length(curric.courses[i].requisites) == 0 + @test length(curric.courses[i].requisites[1]) == 0 elseif c.id == 12 @test curric.courses[i].name == "General Elective" @test curric.courses[i].credit_hours == 3 @test curric.courses[i].institution == "ACME State University" - @test length(curric.courses[i].requisites) == 0 + @test length(curric.courses[i].requisites[1]) == 0 end end # test additional courses @@ -225,7 +225,7 @@ for (i,c) in enumerate(dp.additional_courses) @test dp.additional_courses[i].num == "110" @test dp.additional_courses[i].institution == "ACME State University" @test dp.additional_courses[i].canonical_name == "Precalculus" - @test dp.additional_courses[i].requisites[14] == pre + @test dp.additional_courses[i].requisites[1][14] == pre elseif c.id == 14 @test dp.additional_courses[i].name == "College Algebra" @test dp.additional_courses[i].credit_hours == 3 @@ -233,7 +233,7 @@ for (i,c) in enumerate(dp.additional_courses) @test dp.additional_courses[i].num == "102" @test dp.additional_courses[i].institution == "ACME State University" @test dp.additional_courses[i].canonical_name == "College Algebra" - @test dp.additional_courses[i].requisites[15] == strict_co + @test dp.additional_courses[i].requisites[1][15] == strict_co elseif c.id == 15 @test dp.additional_courses[i].name == "College Algebra Studio" @test dp.additional_courses[i].credit_hours == 1 @@ -241,7 +241,7 @@ for (i,c) in enumerate(dp.additional_courses) @test dp.additional_courses[i].num == "102S" @test dp.additional_courses[i].institution == "ACME State University" @test dp.additional_courses[i].canonical_name == "College Algebra Recitation" - @test length(dp.additional_courses[i].requisites) == 0 + @test length(dp.additional_courses[i].requisites[1]) == 0 elseif c.id == 16 @test dp.additional_courses[i].name == "Hemp Baskets" @test dp.additional_courses[i].credit_hours == 3 @@ -249,7 +249,7 @@ for (i,c) in enumerate(dp.additional_courses) @test dp.additional_courses[i].num == "420" @test dp.additional_courses[i].institution == "ACME State University" @test dp.additional_courses[i].canonical_name == "College Algebra Recitation" - @test dp.additional_courses[i].requisites[6] == co + @test dp.additional_courses[i].requisites[1][6] == co end end # TODO: add learning outcomes diff --git a/test/DataTypes.jl b/test/DataTypes.jl index 25d7ddc..f8b2d8c 100644 --- a/test/DataTypes.jl +++ b/test/DataTypes.jl @@ -29,16 +29,16 @@ include("test_degree_plan.jl") @test course_id(A.prefix, A.num, A.name, A.institution) == convert(Int, mod(hash(A.name * A.prefix * A.num * A.institution), UInt32)) # Test add_requisite! function -@test length(A.requisites) == 0 -@test length(B.requisites) == 0 -@test length(C.requisites) == 3 -@test length(D.requisites) == 0 -@test length(E.requisites) == 1 -@test length(F.requisites) == 1 +@test length(A.requisites[1]) == 0 +@test length(B.requisites[1]) == 0 +@test length(C.requisites[1]) == 3 +@test length(D.requisites[1]) == 0 +@test length(E.requisites[1]) == 1 +@test length(F.requisites[1]) == 1 # Test delete_requisite! function delete_requisite!(A,C); -@test length(C.requisites) == 2 +@test length(C.requisites[1]) == 2 add_requisite!(A,C,pre); # Test Curriciulum creation diff --git a/test/RequirementsAnalytics.jl b/test/RequirementsAnalytics.jl index 46f2812..b72b8f1 100644 --- a/test/RequirementsAnalytics.jl +++ b/test/RequirementsAnalytics.jl @@ -82,10 +82,31 @@ lev = level(program_requirements) @test lev[2605601572] == 2 @test lev[2663541090] == 2 +# not enough credits in the sub-requirements to meet the 41 CH requirement bad_program_requirements = RequirementSet("Program Requirements", 41, program_reqs, description="Degree Requirements for the BS Program") @test is_valid(bad_program_requirements, errors) == false +# not enough credits in the collection of courses to meet 6 CHs bad_dr3 = CourseSet("Humanities Requirement", 6, humanities_courses, description="General Education Humanities Requirement", double_count = true) @test is_valid(bad_dr3, errors) == false +# duplicate requirement sets +dr_dup = RequirementSet("Another Gen. Ed. Core", 13, gen_ed_reqs, description="Identical General Education Requirements") +dup_reqs = RequirementSet("Two gen eds that are identical", 13, [dr4, dr_dup], description="Invalid Requirement Set") +@test is_valid(dup_reqs, errors) == false + +# duplication within requirement sets +gen_ed_reqs_subset = Array{AbstractRequirement,1}() +push!(gen_ed_reqs_subset, dr1, dr2) +subset_reqs = RequirementSet("Subset Gen. Ed. Core", 9, gen_ed_reqs_subset, description="Proper Subset") +bad_reqs_subset = RequirementSet("Overlapping Requirements", 13, [dr4, subset_reqs], description="Gen. Ed. w/ Proper Subset") +@test is_valid(bad_reqs_subset, errors) == false + +subset_courses = CourseSet("Subset Humanities Requirement", 1, [C4 => grade("D")], description="General Education Humanities Requirement subset", double_count = true) +program_reqs = Array{AbstractRequirement,1}() +push!(program_reqs, dr4, dr5, dr6, dr7, dr8, dr11, dr12, subset_courses) +program_requirements = RequirementSet("Program Requirements w/ subset courses", 40, program_reqs, description="Degree Requirements for the BS Program w/ subset courses") +# should be true because we allow duplicate subset requirements (happens in cases like gen ed) +@test is_valid(program_requirements, errors) == true + end \ No newline at end of file